From 7779d4adbadc4e384c2309253b8e447d4099574c Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Mon, 3 Aug 2020 12:01:51 -0700 Subject: [PATCH 1/6] Add support for IngressClass resources --- cmd/nginx-ingress/main.go | 56 ++- deployments/common/ingress-class.yaml | 8 + deployments/helm-chart/README.md | 5 +- .../templates/controller-daemonset.yaml | 2 + .../templates/controller-deployment.yaml | 2 + .../templates/controller-ingress-class..yaml | 12 + deployments/helm-chart/templates/rbac.yaml | 6 + deployments/helm-chart/values.yaml | 21 +- deployments/rbac/rbac.yaml | 6 + .../command-line-arguments.md | 15 +- .../ingress-resources/basic-configuration.md | 10 + .../installation/installation-with-helm.md | 15 +- .../installation-with-manifests.md | 8 + .../running-multiple-ingress-controllers.md | 1 + examples/complete-example/README.md | 2 +- examples/complete-example/cafe-ingress.yaml | 1 + internal/configs/ingress_test.go | 70 ++-- internal/k8s/controller.go | 18 +- internal/k8s/controller_test.go | 69 +++- internal/k8s/handlers.go | 12 +- internal/k8s/status.go | 21 +- internal/k8s/task_queue.go | 6 +- internal/k8s/utils.go | 41 ++- .../apimachinery/pkg/util/version/doc.go | 18 + .../apimachinery/pkg/util/version/version.go | 322 ++++++++++++++++++ vendor/modules.txt | 1 + 26 files changed, 658 insertions(+), 90 deletions(-) create mode 100644 deployments/common/ingress-class.yaml create mode 100644 deployments/helm-chart/templates/controller-ingress-class..yaml create mode 100644 vendor/k8s.io/apimachinery/pkg/util/version/doc.go create mode 100644 vendor/k8s.io/apimachinery/pkg/util/version/version.go diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index 2435f26120..e70003b951 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -30,6 +30,7 @@ import ( api_v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation" + util_version "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -68,13 +69,23 @@ var ( appProtect = flag.Bool("enable-app-protect", false, "Enable support for NGINX App Protect. Requires -nginx-plus.") ingressClass = flag.String("ingress-class", "nginx", - `A class of the Ingress controller. The Ingress controller only processes Ingress resources that belong to its class - - i.e. have the annotation "kubernetes.io/ingress.class" or the "ingressClassName" field in VirtualServer/VirtualServerRoute equal to the class. Additionally, - the Ingress controller processes Ingress resources that do not have that annotation, - which can be disabled by setting the "-use-ingress-class-only" flag`) + `A class of the Ingress controller. + + For Kubernetes >= 1.18, a corresponding IngressClass resource with the name equal to the class must be deployed. Otherwise, + the Ingress Controller will fail to start. + The Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. + + For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class + - i.e have the annotation "kubernetes.io/ingress.class" equal to the class. + Additionally, the Ingress Controller processes resources that do not have the class set, + which can be disabled by setting the "-use-ingress-class-only" flag + + The Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the "ingressClassName" field for all versions of kubernetes.`) useIngressClassOnly = flag.Bool("use-ingress-class-only", false, - `Ignore Ingress resources without the "kubernetes.io/ingress.class" annotation or the "ingressClassName" field in VirtualServer/VirtualServerRoute`) + `For kubernetes versions >= 1.18 this flag will be IGNORED. + + Ignore Ingress resources without the "kubernetes.io/ingress.class" annotation`) defaultServerSecret = flag.String("default-server-tls-secret", "", `A Secret with a TLS certificate and key for TLS termination of the default server. Format: /. @@ -256,6 +267,32 @@ func main() { glog.Fatalf("Failed to create client: %v.", err) } + k8sVersion, err := k8s.GetK8sVersion(kubeClient) + if err != nil { + glog.Fatalf("error retrieving k8s version: %v", err) + } + + minK8sVersion := minVersion("1.14.0") + if !k8sVersion.AtLeast(minK8sVersion) { + glog.Fatalf("Versions of Kubernetes < %v are not supported, please refer to the documentation for details on supported versions.", minK8sVersion) + } + + // Ingress V1 is only available from k8s > 1.18 + ingressV1Version := minVersion("1.18.0") + if k8sVersion.AtLeast(ingressV1Version) { + *useIngressClassOnly = true + glog.Warningln("The '-use-ingress-class-only' flag will be deprecated and has no effect on versions of kubernetes >= 1.18.0. Processing ONLY resources that have the 'ingressClassName' field in Ingress equal to the class.") + + ingressClassRes, err := kubeClient.NetworkingV1beta1().IngressClasses().Get(context.TODO(), *ingressClass, meta_v1.GetOptions{}) + if err != nil { + glog.Fatalf("Error when getting IngressClass %v: %v", *ingressClass, err) + } + + if ingressClassRes != nil && ingressClassRes.Spec.Controller != k8s.IngressControllerName { + glog.Fatalf("IngressClass with name %v has an invalid Spec.Controller %v", ingressClassRes.Name, ingressClassRes.Spec.Controller) + } + } + var dynClient dynamic.Interface if *appProtect { dynClient, err = dynamic.NewForConfig(config) @@ -789,3 +826,12 @@ func ready(lbc *k8s.LoadBalancerController) http.HandlerFunc { fmt.Fprintln(w, "Ready") } } + +func minVersion(min string) (v *util_version.Version) { + minVer, err := util_version.ParseGeneric(min) + if err != nil { + glog.Fatalf("unexpected error parsing minimum supported version: %v", err) + } + + return minVer +} diff --git a/deployments/common/ingress-class.yaml b/deployments/common/ingress-class.yaml new file mode 100644 index 0000000000..f98fd68381 --- /dev/null +++ b/deployments/common/ingress-class.yaml @@ -0,0 +1,8 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: nginx + # annotations: + # ingressclass.kubernetes.io/is-default-class: "true" +spec: + controller: nginx.org/ingress-controller \ No newline at end of file diff --git a/deployments/helm-chart/README.md b/deployments/helm-chart/README.md index a8c5228b4d..e833f6c58d 100644 --- a/deployments/helm-chart/README.md +++ b/deployments/helm-chart/README.md @@ -214,8 +214,9 @@ Parameter | Description | Default `controller.volumeMounts` | The volumeMounts of the Ingress controller pods. | [] `controller.resources` | The resources of the Ingress controller pods. | {} `controller.replicaCount` | The number of replicas of the Ingress controller deployment. | 1 -`controller.ingressClass` | A class of the Ingress controller. The Ingress controller only processes Ingress resources that belong to its class - i.e. have the annotation `"kubernetes.io/ingress.class"` or the `"ingressClassName"` field in VirtualServer/VirtualServerRoute equal to the class. Additionally, the Ingress controller processes Ingress resources that do not have that annotation which can be disabled by setting the "-use-ingress-class-only" flag. | nginx -`controller.useIngressClassOnly` | Ignore Ingress resources without the `"kubernetes.io/ingress.class"` annotation or the `"ingressClassName"` field in VirtualServer/VirtualServerRoute. | false +`controller.ingressClass` | A class of the Ingress controller. For Kubernetes >= 1.18, the Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. Additionally the Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the "ingressClassName" field. For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class - i.e have the annotation "kubernetes.io/ingress.class" (for Ingress resources) or field "ingressClassName" (for VirtualServer/VirtualServerRoute resources) equal to the class. Additionally, the Ingress Controller processes resources that do not have the class set, which can be disabled by setting the "-use-ingress-class-only" flag. | nginx +`controller.useIngressClassOnly` | Ignore Ingress resources without the `"kubernetes.io/ingress.class"` annotation. For kubernetes versions >= 1.18 this flag will be IGNORED. | false +`controller.setAsDefaultIngress` | New Ingresses without an `"ingressClassName"` field specified will be assigned the class specified in `controller.ingressClass`. Only for kubernetes versions >= 1.18. | false `controller.watchNamespace` | Namespace to watch for Ingress resources. By default the Ingress controller watches all namespaces. | "" `controller.enableCustomResources` | Enable the custom resources. | true `controller.enableTLSPassthrough` | Enable TLS Passthrough on port 443. Requires `controller.enableCustomResources`. | false diff --git a/deployments/helm-chart/templates/controller-daemonset.yaml b/deployments/helm-chart/templates/controller-daemonset.yaml index 1f58b6a9bf..39d9dba2b9 100644 --- a/deployments/helm-chart/templates/controller-daemonset.yaml +++ b/deployments/helm-chart/templates/controller-daemonset.yaml @@ -108,7 +108,9 @@ spec: - -default-server-tls-secret=$(POD_NAMESPACE)/{{ include "nginx-ingress.defaultTLSName" . }} {{- end }} - -ingress-class={{ .Values.controller.ingressClass }} +{{- if semverCompare "<1.18.0" .Capabilities.KubeVersion.GitVersion }} - -use-ingress-class-only={{ .Values.controller.useIngressClassOnly }} +{{- end }} {{- if .Values.controller.watchNamespace }} - -watch-namespace={{ .Values.controller.watchNamespace }} {{- end }} diff --git a/deployments/helm-chart/templates/controller-deployment.yaml b/deployments/helm-chart/templates/controller-deployment.yaml index 6b952d25f9..65c79eff0a 100644 --- a/deployments/helm-chart/templates/controller-deployment.yaml +++ b/deployments/helm-chart/templates/controller-deployment.yaml @@ -106,7 +106,9 @@ spec: - -default-server-tls-secret=$(POD_NAMESPACE)/{{ include "nginx-ingress.defaultTLSName" . }} {{- end }} - -ingress-class={{ .Values.controller.ingressClass }} +{{- if semverCompare "<1.18.0" .Capabilities.KubeVersion.GitVersion }} - -use-ingress-class-only={{ .Values.controller.useIngressClassOnly }} +{{- end }} {{- if .Values.controller.watchNamespace }} - -watch-namespace={{ .Values.controller.watchNamespace }} {{- end }} diff --git a/deployments/helm-chart/templates/controller-ingress-class..yaml b/deployments/helm-chart/templates/controller-ingress-class..yaml new file mode 100644 index 0000000000..0b3cc21ba1 --- /dev/null +++ b/deployments/helm-chart/templates/controller-ingress-class..yaml @@ -0,0 +1,12 @@ +{{- if semverCompare ">=1.18.0" .Capabilities.KubeVersion.GitVersion }} +apiVersion: networking.k8s.io/v1beta1 +kind: IngressClass +metadata: + name: {{ .Values.controller.ingressClass }} +{{- if .Values.controller.setAsDefaultIngress }} + annotations: + ingressclass.kubernetes.io/is-default-class: "true" +{{- end }} +spec: + controller: nginx.org/ingress-controller +{{- end }} \ No newline at end of file diff --git a/deployments/helm-chart/templates/rbac.yaml b/deployments/helm-chart/templates/rbac.yaml index ff0a63247d..41806d2578 100644 --- a/deployments/helm-chart/templates/rbac.yaml +++ b/deployments/helm-chart/templates/rbac.yaml @@ -69,6 +69,12 @@ rules: - get - list - watch +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get {{- if .Values.controller.reportIngressStatus.enable }} - apiGroups: - networking.k8s.io diff --git a/deployments/helm-chart/values.yaml b/deployments/helm-chart/values.yaml index 16e14ba373..d42306a5d2 100644 --- a/deployments/helm-chart/values.yaml +++ b/deployments/helm-chart/values.yaml @@ -117,14 +117,27 @@ controller: ## The number of replicas of the Ingress controller deployment. replicaCount: 1 - ## A class of the Ingress controller. The Ingress controller only processes Ingress resources that belong to its class - ## i.e. have the annotation "kubernetes.io/ingress.class" or the "ingressClassName" field in VirtualServer/VirtualServerRoute equal to the class. - ## Additionally, the Ingress controller processes Ingress resources that do not have that annotation which can be disabled by setting the "-use-ingress-class-only" flag. + ## A class of the Ingress controller. + + ## For Kubernetes >= 1.18, a corresponding IngressClass resource with the name equal to the class must be deployed. Otherwise, + ## the Ingress Controller will fail to start. + ## The Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. + + ## For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class + ## - i.e have the annotation "kubernetes.io/ingress.class" equal to the class. + ## Additionally, the Ingress Controller processes resources that do not have the class set, which can be disabled by setting the "-use-ingress-class-only" flag + + ## The Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the "ingressClassName" field for all versions of kubernetes. ingressClass: nginx - ## Ignore Ingress resources without the "kubernetes.io/ingress.class" annotation or the "ingressClassName" field in VirtualServer/VirtualServerRoute. + ## For kubernetes versions >= 1.18 this flag will be IGNORED. + ## Ignore Ingress resources without the "kubernetes.io/ingress.class" annotation useIngressClassOnly: false + ## Only for Kubernetes >= 1.18 + ## New Ingresses without an ingressClassName field specified will be assigned the class specified in `controller.ingressClass`. + setAsDefaultIngress: false + ## Namespace to watch for Ingress resources. By default the Ingress controller watches all namespaces. watchNamespace: "" diff --git a/deployments/rbac/rbac.yaml b/deployments/rbac/rbac.yaml index 16d787bb96..822d91b557 100644 --- a/deployments/rbac/rbac.yaml +++ b/deployments/rbac/rbac.yaml @@ -78,6 +78,12 @@ rules: - virtualserverroutes/status verbs: - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 diff --git a/docs-web/configuration/global-configuration/command-line-arguments.md b/docs-web/configuration/global-configuration/command-line-arguments.md index 7a8c3d955a..f2574ad05f 100644 --- a/docs-web/configuration/global-configuration/command-line-arguments.md +++ b/docs-web/configuration/global-configuration/command-line-arguments.md @@ -66,8 +66,15 @@ Below we describe the available command-line arguments: .. option:: -ingress-class - A class of the Ingress controller. The Ingress controller only processes Ingress resources that belong to its class (i.e. have the annotation "kubernetes.io/ingress.class" or the "ingressClassName" field in VirtualServer/VirtualServerRoute"). - Additionally, the Ingress controller processes Ingress resources that do not have that annotation, which can be disabled by setting the :option:`-use-ingress-class-only` flag (default "nginx"). + A class of the Ingress controller. + + For Kubernetes >= 1.18, a corresponding IngressClass resource with the name equal to the class must be deployed. Otherwise, the Ingress Controller will fail to start. + The Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. + + For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class - i.e have the annotation "kubernetes.io/ingress.class" (for Ingress resources) or field "ingressClassName" (for VirtualServer/VirtualServerRoute resources) equal to the class. + Additionally, the Ingress Controller processes resources that do not have the class set, which can be disabled by setting the "-use-ingress-class-only" flag (default "nginx"). + + The Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the "ingressClassName" field. .. option:: -ingress-template-path @@ -140,7 +147,9 @@ Below we describe the available command-line arguments: .. option:: -use-ingress-class-only - Ignore Ingress resources without the "kubernetes.io/ingress.class" annotation or the "ingressClassName" field in VirtualServer/VirtualServerRoute. + For kubernetes versions >= 1.18 this flag will be IGNORED. + + Ignore Ingress resources without the "kubernetes.io/ingress.class" annotation. .. option:: -v diff --git a/docs-web/configuration/ingress-resources/basic-configuration.md b/docs-web/configuration/ingress-resources/basic-configuration.md index 6427430afd..1126708042 100644 --- a/docs-web/configuration/ingress-resources/basic-configuration.md +++ b/docs-web/configuration/ingress-resources/basic-configuration.md @@ -63,6 +63,16 @@ Starting from Kubernetes 1.18, you can use the following new features: serviceName: coffee-svc servicePort: 80 ``` +* The `ingressClassName` field is now supported. When using this filed you need to create the `IngressClass` resource with the corresponding `name`, for example create the following resource: + ```yaml + apiVersion: networking.k8s.io/v1beta1 + kind: IngressClass + metadata: + name: nginx + spec: + controller: nginx.org/ingress-controller + ``` + and then set `spec.IngressClassName` to `nginx` in the `Ingress` resource. ## Restrictions diff --git a/docs-web/installation/installation-with-helm.md b/docs-web/installation/installation-with-helm.md index b6d0756fa7..3fbb8800d4 100644 --- a/docs-web/installation/installation-with-helm.md +++ b/docs-web/installation/installation-with-helm.md @@ -274,10 +274,21 @@ The following tables lists the configurable parameters of the NGINX Ingress cont - The number of replicas of the Ingress controller deployment. - 1 * - ``controller.ingressClass`` - - A class of the Ingress controller. The Ingress controller only processes Ingress resources that belong to its class - i.e. have the annotation ``"kubernetes.io/ingress.class"`` or the ``"ingressClassName"`` field in VirtualServer/VirtualServerRoute equal to the class. Additionally, the Ingress controller processes Ingress resources that do not have that annotation which can be disabled by setting the "-use-ingress-class-only" flag. + - A class of the Ingress controller. + For Kubernetes >= 1.18, a corresponding IngressClass resource with the name equal to the class must be deployed. Otherwise, + the Ingress Controller will fail to start. + The Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. + + For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class - i.e have the annotation "kubernetes.io/ingress.class" equal to the class. + Additionally, the Ingress Controller processes resources that do not have the class set, which can be disabled by setting the "-use-ingress-class-only" flag. + + The Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the "ingressClassName" field for all versions of kubernetes. - nginx * - ``controller.useIngressClassOnly`` - - Ignore Ingress resources without the ``"kubernetes.io/ingress.class"`` annotation or the ``"ingressClassName"`` field in VirtualServer/VirtualServerRoute. + - Ignore Ingress resources without the ``"kubernetes.io/ingress.class"`` annotation. For kubernetes versions >= 1.18 this flag will be IGNORED. + - false + * - ``controller.setAsDefaultIngress`` + - New Ingresses without an ingressClassName field specified will be assigned the class specified in `controller.ingressClass`. - false * - ``controller.watchNamespace`` - Namespace to watch for Ingress resources. By default the Ingress controller watches all namespaces. diff --git a/docs-web/installation/installation-with-manifests.md b/docs-web/installation/installation-with-manifests.md index ea3e3e9a70..9903c38b20 100644 --- a/docs-web/installation/installation-with-manifests.md +++ b/docs-web/installation/installation-with-manifests.md @@ -47,6 +47,14 @@ In this section, we create resources common for most of the Ingress Controller i $ kubectl apply -f common/nginx-config.yaml ``` +1. Create an IngressClass resource (for Kubernetes >= 1.18): + ``` + $ kubectl apply -f common/ingress-class.yaml + ``` + If you would like to set the Ingress Controller as the default ingress, uncomment the annotation `ingressclass.kubernetes.io/is-default-class`. With this annotation set to true all the new Ingresses without an ingressClassName field specified will be assigned this IngressClass. + + ***Note**: The Ingress Controller will fail to start without an IngressClass resource. + ### Create Custom Resources **Note**: If you're using Kubernetes 1.14, make sure to add `--validate=false` to the `kubectl apply` commands below. Otherwise, you will get an error validating data: diff --git a/docs-web/installation/running-multiple-ingress-controllers.md b/docs-web/installation/running-multiple-ingress-controllers.md index f6a84c3329..4048dfa69d 100644 --- a/docs-web/installation/running-multiple-ingress-controllers.md +++ b/docs-web/installation/running-multiple-ingress-controllers.md @@ -12,6 +12,7 @@ This document explains the following topics: The smooth coexistence of multiple Ingress Controllers in one cluster is provided by the Ingress class concept, which mandates the following: * Every Ingress Controller must only handle Ingress resources for its particular class. * Ingress resources should be annotated with the `kubernetes.io/ingress.class` annotation set to the value, which corresponds to the class of the Ingress Controller the user wants to use. +* When using versions of Kubernetes >= 1.18, Ingress resources should have the `ingressClassName` field set to the value, which corresponds to the class of the Ingress Controller the user wants to use. * VirtualServer and VirtualServerRoute resources should have the `ingressClassName` field set to the value, which corresponds to the class of the Ingress Controller the user wants to use. ### Configuring Ingress Class diff --git a/examples/complete-example/README.md b/examples/complete-example/README.md index e58c251142..942207aa6f 100644 --- a/examples/complete-example/README.md +++ b/examples/complete-example/README.md @@ -31,7 +31,7 @@ $ kubectl create -f cafe.yaml $ kubectl create -f cafe-secret.yaml ``` -2. Create an Ingress resource: +2. Create an Ingress resource (for Kubernetes >= 1.18, uncomment the ```ingressClassName``` field in the YAML file): ``` $ kubectl create -f cafe-ingress.yaml ``` diff --git a/examples/complete-example/cafe-ingress.yaml b/examples/complete-example/cafe-ingress.yaml index 89e955bf94..2eca43d223 100644 --- a/examples/complete-example/cafe-ingress.yaml +++ b/examples/complete-example/cafe-ingress.yaml @@ -3,6 +3,7 @@ kind: Ingress metadata: name: cafe-ingress spec: + # ingressClassName: nginx # use only with k8s version >= 1.18.0 tls: - hosts: - cafe.example.com diff --git a/internal/configs/ingress_test.go b/internal/configs/ingress_test.go index 5e2eeef397..f93109cea6 100644 --- a/internal/configs/ingress_test.go +++ b/internal/configs/ingress_test.go @@ -6,7 +6,7 @@ import ( "testing" v1 "k8s.io/api/core/v1" - "k8s.io/api/networking/v1beta1" + networking "k8s.io/api/networking/v1beta1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -128,11 +128,11 @@ func TestPathOrDefaultReturnActual(t *testing.T) { } func TestGenerateIngressPath(t *testing.T) { - exact := v1beta1.PathTypeExact - prefix := v1beta1.PathTypePrefix - impSpec := v1beta1.PathTypeImplementationSpecific + exact := networking.PathTypeExact + prefix := networking.PathTypePrefix + impSpec := networking.PathTypeImplementationSpecific tests := []struct { - pathType *v1beta1.PathType + pathType *networking.PathType path string expected string }{ @@ -248,7 +248,7 @@ func createExpectedConfigForCafeIngressEx() version1.IngressNginxConfig { } func createCafeIngressEx() IngressEx { - cafeIngress := v1beta1.Ingress{ + cafeIngress := networking.Ingress{ ObjectMeta: meta_v1.ObjectMeta{ Name: "cafe-ingress", Namespace: "default", @@ -256,29 +256,29 @@ func createCafeIngressEx() IngressEx { "kubernetes.io/ingress.class": "nginx", }, }, - Spec: v1beta1.IngressSpec{ - TLS: []v1beta1.IngressTLS{ + Spec: networking.IngressSpec{ + TLS: []networking.IngressTLS{ { Hosts: []string{"cafe.example.com"}, SecretName: "cafe-secret", }, }, - Rules: []v1beta1.IngressRule{ + Rules: []networking.IngressRule{ { Host: "cafe.example.com", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ { Path: "/coffee", - Backend: v1beta1.IngressBackend{ + Backend: networking.IngressBackend{ ServiceName: "coffee-svc", ServicePort: intstr.FromString("80"), }, }, { Path: "/tea", - Backend: v1beta1.IngressBackend{ + Backend: networking.IngressBackend{ ServiceName: "tea-svc", ServicePort: intstr.FromString("80"), }, @@ -426,7 +426,7 @@ func TestGenerateNginxCfgForMergeableIngressesForJWT(t *testing.T) { } func createMergeableCafeIngress() *MergeableIngresses { - master := v1beta1.Ingress{ + master := networking.Ingress{ ObjectMeta: meta_v1.ObjectMeta{ Name: "cafe-ingress-master", Namespace: "default", @@ -435,19 +435,19 @@ func createMergeableCafeIngress() *MergeableIngresses { "nginx.org/mergeable-ingress-type": "master", }, }, - Spec: v1beta1.IngressSpec{ - TLS: []v1beta1.IngressTLS{ + Spec: networking.IngressSpec{ + TLS: []networking.IngressTLS{ { Hosts: []string{"cafe.example.com"}, SecretName: "cafe-secret", }, }, - Rules: []v1beta1.IngressRule{ + Rules: []networking.IngressRule{ { Host: "cafe.example.com", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ // HTTP must not be nil for Master - Paths: []v1beta1.HTTPIngressPath{}, + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ // HTTP must not be nil for Master + Paths: []networking.HTTPIngressPath{}, }, }, }, @@ -455,7 +455,7 @@ func createMergeableCafeIngress() *MergeableIngresses { }, } - coffeeMinion := v1beta1.Ingress{ + coffeeMinion := networking.Ingress{ ObjectMeta: meta_v1.ObjectMeta{ Name: "cafe-ingress-coffee-minion", Namespace: "default", @@ -464,16 +464,16 @@ func createMergeableCafeIngress() *MergeableIngresses { "nginx.org/mergeable-ingress-type": "minion", }, }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ { Host: "cafe.example.com", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ { Path: "/coffee", - Backend: v1beta1.IngressBackend{ + Backend: networking.IngressBackend{ ServiceName: "coffee-svc", ServicePort: intstr.FromString("80"), }, @@ -486,7 +486,7 @@ func createMergeableCafeIngress() *MergeableIngresses { }, } - teaMinion := v1beta1.Ingress{ + teaMinion := networking.Ingress{ ObjectMeta: meta_v1.ObjectMeta{ Name: "cafe-ingress-tea-minion", Namespace: "default", @@ -495,16 +495,16 @@ func createMergeableCafeIngress() *MergeableIngresses { "nginx.org/mergeable-ingress-type": "minion", }, }, - Spec: v1beta1.IngressSpec{ - Rules: []v1beta1.IngressRule{ + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ { Host: "cafe.example.com", - IngressRuleValue: v1beta1.IngressRuleValue{ - HTTP: &v1beta1.HTTPIngressRuleValue{ - Paths: []v1beta1.HTTPIngressPath{ + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ { Path: "/tea", - Backend: v1beta1.IngressBackend{ + Backend: networking.IngressBackend{ ServiceName: "tea-svc", ServicePort: intstr.FromString("80"), }, diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 76864404be..52346c3e22 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -27,7 +27,6 @@ import ( "github.com/golang/glog" "github.com/spiffe/go-spiffe/workload" - "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" @@ -60,6 +59,8 @@ import ( const ( ingressClassKey = "kubernetes.io/ingress.class" + // IngressControllerName holds Ingress Controller name + IngressControllerName = "nginx.org/ingress-controller" ) var ( @@ -287,7 +288,7 @@ func NewLoadBalancerController(input NewLoadBalancerControllerInput) *LoadBalanc } // UpdateManagedAndMergeableIngresses invokes the UpdateManagedAndMergeableIngresses method on the Status Updater -func (lbc *LoadBalancerController) UpdateManagedAndMergeableIngresses(ingresses []v1beta1.Ingress, mergeableIngresses map[string]*configs.MergeableIngresses) error { +func (lbc *LoadBalancerController) UpdateManagedAndMergeableIngresses(ingresses []networking.Ingress, mergeableIngresses map[string]*configs.MergeableIngresses) error { return lbc.statusUpdater.UpdateManagedAndMergeableIngresses(ingresses, mergeableIngresses) } @@ -493,6 +494,7 @@ func (lbc *LoadBalancerController) Run() { go lbc.configMapController.Run(lbc.ctx.Done()) } go lbc.ingressController.Run(lbc.ctx.Done()) + if lbc.areCustomResourcesEnabled { go lbc.virtualServerController.Run(lbc.ctx.Done()) go lbc.virtualServerRouteController.Run(lbc.ctx.Done()) @@ -3032,6 +3034,7 @@ func (lbc *LoadBalancerController) getServiceForIngressBackend(backend *networki // HasCorrectIngressClass checks if resource ingress class annotation (if exists) or ingressClass string for VS/VSR is matching with ingress controller class func (lbc *LoadBalancerController) HasCorrectIngressClass(obj interface{}) bool { var class string + var isIngress bool switch obj.(type) { case *conf_v1.VirtualServer: vs := obj.(*conf_v1.VirtualServer) @@ -3040,13 +3043,22 @@ func (lbc *LoadBalancerController) HasCorrectIngressClass(obj interface{}) bool vsr := obj.(*conf_v1.VirtualServerRoute) class = vsr.Spec.IngressClass case *networking.Ingress: + isIngress = true ing := obj.(*networking.Ingress) class = ing.Annotations[ingressClassKey] + if class == "" && ing.Spec.IngressClassName != nil { + class = *ing.Spec.IngressClassName + } else { + // the annotation takes precedence over the field + glog.Warningln("Using the DEPRECATED annotation 'kubernetes.io/ingress.class'. The 'ingressClassName' field will be ignored.") + } + default: return false } - if lbc.useIngressClassOnly { + // useIngressClassOnly only applies for Ingress resources + if lbc.useIngressClassOnly && isIngress { return class == lbc.ingressClass } return class == lbc.ingressClass || class == "" diff --git a/internal/k8s/controller_test.go b/internal/k8s/controller_test.go index 6b2efd61c4..cb047d58b0 100644 --- a/internal/k8s/controller_test.go +++ b/internal/k8s/controller_test.go @@ -28,6 +28,8 @@ import ( func TestHasCorrectIngressClass(t *testing.T) { ingressClass := "ing-ctrl" + incorrectIngressClass := "gce" + emptyClass := "" var testsWithoutIngressClassOnly = []struct { lbc *LoadBalancerController @@ -42,7 +44,7 @@ func TestHasCorrectIngressClass(t *testing.T) { }, &networking.Ingress{ ObjectMeta: meta_v1.ObjectMeta{ - Annotations: map[string]string{ingressClassKey: ""}, + Annotations: map[string]string{ingressClassKey: emptyClass}, }, }, true, @@ -55,7 +57,7 @@ func TestHasCorrectIngressClass(t *testing.T) { }, &networking.Ingress{ ObjectMeta: meta_v1.ObjectMeta{ - Annotations: map[string]string{ingressClassKey: "gce"}, + Annotations: map[string]string{ingressClassKey: incorrectIngressClass}, }, }, false, @@ -101,7 +103,7 @@ func TestHasCorrectIngressClass(t *testing.T) { }, &networking.Ingress{ ObjectMeta: meta_v1.ObjectMeta{ - Annotations: map[string]string{ingressClassKey: ""}, + Annotations: map[string]string{ingressClassKey: emptyClass}, }, }, false, @@ -114,7 +116,7 @@ func TestHasCorrectIngressClass(t *testing.T) { }, &networking.Ingress{ ObjectMeta: meta_v1.ObjectMeta{ - Annotations: map[string]string{ingressClassKey: "gce"}, + Annotations: map[string]string{ingressClassKey: incorrectIngressClass}, }, }, false, @@ -145,6 +147,61 @@ func TestHasCorrectIngressClass(t *testing.T) { }, false, }, + { + &LoadBalancerController{ + ingressClass: ingressClass, + useIngressClassOnly: true, // always true for k8s >= 1.18 + metricsCollector: collectors.NewControllerFakeCollector(), + }, + &networking.Ingress{ + Spec: networking.IngressSpec{ + IngressClassName: &incorrectIngressClass, + }, + }, + false, + }, + { + &LoadBalancerController{ + ingressClass: ingressClass, + useIngressClassOnly: true, // always true for k8s >= 1.18 + metricsCollector: collectors.NewControllerFakeCollector(), + }, + &networking.Ingress{ + Spec: networking.IngressSpec{ + IngressClassName: &emptyClass, + }, + }, + false, + }, + { + &LoadBalancerController{ + ingressClass: ingressClass, + useIngressClassOnly: true, // always true for k8s >= 1.18 + metricsCollector: collectors.NewControllerFakeCollector(), + }, + &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Annotations: map[string]string{ingressClassKey: incorrectIngressClass}, + }, + Spec: networking.IngressSpec{ + IngressClassName: &ingressClass, + }, + }, + false, + }, + { + &LoadBalancerController{ + ingressClass: ingressClass, + useIngressClassOnly: true, // always true for k8s >= 1.18 + metricsCollector: collectors.NewControllerFakeCollector(), + }, + &networking.Ingress{ + Spec: networking.IngressSpec{ + IngressClassName: &ingressClass, + }, + }, + true, + }, } for _, test := range testsWithoutIngressClassOnly { @@ -191,7 +248,7 @@ func TestHasCorrectIngressClassVS(t *testing.T) { IngressClass: "", }, }, - false, + true, }, { lbcIngOnlyTrue, @@ -214,7 +271,7 @@ func TestHasCorrectIngressClassVS(t *testing.T) { { lbcIngOnlyTrue, &conf_v1.VirtualServer{}, - false, + true, }, } diff --git a/internal/k8s/handlers.go b/internal/k8s/handlers.go index a7e1063dc9..df068c4fdc 100644 --- a/internal/k8s/handlers.go +++ b/internal/k8s/handlers.go @@ -6,7 +6,7 @@ import ( "github.com/golang/glog" v1 "k8s.io/api/core/v1" - "k8s.io/api/networking/v1beta1" + networking "k8s.io/api/networking/v1beta1" "k8s.io/client-go/tools/cache" "fmt" @@ -96,7 +96,7 @@ func createEndpointHandlers(lbc *LoadBalancerController) cache.ResourceEventHand func createIngressHandlers(lbc *LoadBalancerController) cache.ResourceEventHandlerFuncs { return cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { - ingress := obj.(*v1beta1.Ingress) + ingress := obj.(*networking.Ingress) if !lbc.HasCorrectIngressClass(ingress) { glog.Infof("Ignoring Ingress %v based on Annotation %v", ingress.Name, ingressClassKey) return @@ -105,14 +105,14 @@ func createIngressHandlers(lbc *LoadBalancerController) cache.ResourceEventHandl lbc.AddSyncQueue(obj) }, DeleteFunc: func(obj interface{}) { - ingress, isIng := obj.(*v1beta1.Ingress) + ingress, isIng := obj.(*networking.Ingress) if !isIng { deletedState, ok := obj.(cache.DeletedFinalStateUnknown) if !ok { glog.V(3).Infof("Error received unexpected object: %v", obj) return } - ingress, ok = deletedState.Obj.(*v1beta1.Ingress) + ingress, ok = deletedState.Obj.(*networking.Ingress) if !ok { glog.V(3).Infof("Error DeletedFinalStateUnknown contained non-Ingress object: %v", deletedState.Obj) return @@ -135,8 +135,8 @@ func createIngressHandlers(lbc *LoadBalancerController) cache.ResourceEventHandl } }, UpdateFunc: func(old, current interface{}) { - c := current.(*v1beta1.Ingress) - o := old.(*v1beta1.Ingress) + c := current.(*networking.Ingress) + o := old.(*networking.Ingress) if !lbc.HasCorrectIngressClass(c) { return } diff --git a/internal/k8s/status.go b/internal/k8s/status.go index 7fa9ef9a18..03076b3360 100644 --- a/internal/k8s/status.go +++ b/internal/k8s/status.go @@ -14,11 +14,12 @@ import ( v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" k8s_nginx "github.com/nginxinc/kubernetes-ingress/pkg/client/clientset/versioned" api_v1 "k8s.io/api/core/v1" - "k8s.io/api/networking/v1beta1" + networking "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + typednetworking "k8s.io/client-go/kubernetes/typed/networking/v1beta1" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" - networkingv1beta1 "k8s.io/client-go/kubernetes/typed/networking/v1beta1" ) // statusUpdater reports Ingress, VirtualServer and VirtualServerRoute status information via the kubernetes @@ -39,8 +40,8 @@ type statusUpdater struct { } // UpdateManagedAndMergeableIngresses handles the full return format of LoadBalancerController.getManagedIngresses -func (su *statusUpdater) UpdateManagedAndMergeableIngresses(managedIngresses []v1beta1.Ingress, mergableIngExes map[string]*configs.MergeableIngresses) error { - ings := []v1beta1.Ingress{} +func (su *statusUpdater) UpdateManagedAndMergeableIngresses(managedIngresses []networking.Ingress, mergableIngExes map[string]*configs.MergeableIngresses) error { + ings := []networking.Ingress{} ings = append(ings, managedIngresses...) for _, mergableIngEx := range mergableIngExes { for _, minion := range mergableIngEx.Minions { @@ -52,7 +53,7 @@ func (su *statusUpdater) UpdateManagedAndMergeableIngresses(managedIngresses []v // UpdateMergableIngresses is a convience passthru to update Ingresses with our configs.MergableIngresses type func (su *statusUpdater) UpdateMergableIngresses(mergableIngresses *configs.MergeableIngresses) error { - ings := []v1beta1.Ingress{} + ings := []networking.Ingress{} ingExes := []*configs.IngressEx{} ingExes = append(ingExes, mergableIngresses.Master) @@ -65,17 +66,17 @@ func (su *statusUpdater) UpdateMergableIngresses(mergableIngresses *configs.Merg } // ClearIngressStatus clears the Ingress status. -func (su *statusUpdater) ClearIngressStatus(ing v1beta1.Ingress) error { +func (su *statusUpdater) ClearIngressStatus(ing networking.Ingress) error { return su.updateIngressWithStatus(ing, []api_v1.LoadBalancerIngress{}) } // UpdateIngressStatus updates the status on the selected Ingress. -func (su *statusUpdater) UpdateIngressStatus(ing v1beta1.Ingress) error { +func (su *statusUpdater) UpdateIngressStatus(ing networking.Ingress) error { return su.updateIngressWithStatus(ing, su.status) } // updateIngressWithStatus sets the provided status on the selected Ingress. -func (su *statusUpdater) updateIngressWithStatus(ing v1beta1.Ingress, status []api_v1.LoadBalancerIngress) error { +func (su *statusUpdater) updateIngressWithStatus(ing networking.Ingress, status []api_v1.LoadBalancerIngress) error { if reflect.DeepEqual(ing.Status.LoadBalancer.Ingress, status) { return nil } @@ -114,7 +115,7 @@ func (su *statusUpdater) updateIngressWithStatus(ing v1beta1.Ingress, status []a // BulkUpdateIngressStatus sets the status field on the selected Ingresses, specifically // the External IP field. -func (su *statusUpdater) BulkUpdateIngressStatus(ings []v1beta1.Ingress) error { +func (su *statusUpdater) BulkUpdateIngressStatus(ings []networking.Ingress) error { if len(ings) < 1 { glog.V(3).Info("no ingresses to update") return nil @@ -135,7 +136,7 @@ func (su *statusUpdater) BulkUpdateIngressStatus(ings []v1beta1.Ingress) error { // retryStatusUpdate fetches a fresh copy of the Ingress from the k8s API, checks if it still needs to be // updated, and then attempts to update. We often need to fetch fresh copies due to the // k8s API using ResourceVersion to stop updates on stale items. -func (su *statusUpdater) retryStatusUpdate(clientIngress networkingv1beta1.IngressInterface, ingCopy *v1beta1.Ingress) error { +func (su *statusUpdater) retryStatusUpdate(clientIngress typednetworking.IngressInterface, ingCopy *networking.Ingress) error { apiIng, err := clientIngress.Get(context.TODO(), ingCopy.Name, metav1.GetOptions{}) if err != nil { glog.V(3).Infof("error getting ingress resource: %v", err) diff --git a/internal/k8s/task_queue.go b/internal/k8s/task_queue.go index 8e6d08ad3c..5729cbbac5 100644 --- a/internal/k8s/task_queue.go +++ b/internal/k8s/task_queue.go @@ -8,7 +8,7 @@ import ( conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" conf_v1alpha1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1alpha1" v1 "k8s.io/api/core/v1" - "k8s.io/api/networking/v1beta1" + networking "k8s.io/api/networking/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/workqueue" @@ -142,8 +142,8 @@ type task struct { func newTask(key string, obj interface{}) (task, error) { var k kind switch t := obj.(type) { - case *v1beta1.Ingress: - ing := obj.(*v1beta1.Ingress) + case *networking.Ingress: + ing := obj.(*networking.Ingress) if isMinion(ing) { k = ingressMinion } else { diff --git a/internal/k8s/utils.go b/internal/k8s/utils.go index 1037506b3a..98d8e8e34c 100644 --- a/internal/k8s/utils.go +++ b/internal/k8s/utils.go @@ -21,10 +21,14 @@ import ( "reflect" "strings" + "github.com/golang/glog" v1 "k8s.io/api/core/v1" - "k8s.io/api/networking/v1beta1" + networking "k8s.io/api/networking/v1beta1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/version" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" ) @@ -36,28 +40,28 @@ type storeToIngressLister struct { // GetByKeySafe calls Store.GetByKeySafe and returns a copy of the ingress so it is // safe to modify. -func (s *storeToIngressLister) GetByKeySafe(key string) (ing *v1beta1.Ingress, exists bool, err error) { +func (s *storeToIngressLister) GetByKeySafe(key string) (ing *networking.Ingress, exists bool, err error) { item, exists, err := s.Store.GetByKey(key) if !exists || err != nil { return nil, exists, err } - ing = item.(*v1beta1.Ingress).DeepCopy() + ing = item.(*networking.Ingress).DeepCopy() return } // List lists all Ingress' in the store. -func (s *storeToIngressLister) List() (ing v1beta1.IngressList, err error) { +func (s *storeToIngressLister) List() (ing networking.IngressList, err error) { for _, m := range s.Store.List() { - ing.Items = append(ing.Items, *(m.(*v1beta1.Ingress)).DeepCopy()) + ing.Items = append(ing.Items, *(m.(*networking.Ingress)).DeepCopy()) } return ing, nil } // GetServiceIngress gets all the Ingress' that have rules pointing to a service. // Note that this ignores services without the right nodePorts. -func (s *storeToIngressLister) GetServiceIngress(svc *v1.Service) (ings []v1beta1.Ingress, err error) { +func (s *storeToIngressLister) GetServiceIngress(svc *v1.Service) (ings []networking.Ingress, err error) { for _, m := range s.Store.List() { - ing := *m.(*v1beta1.Ingress).DeepCopy() + ing := *m.(*networking.Ingress).DeepCopy() if ing.Namespace != svc.Namespace { continue } @@ -154,17 +158,17 @@ type storeToSecretLister struct { } // isMinion determines is an ingress is a minion or not -func isMinion(ing *v1beta1.Ingress) bool { +func isMinion(ing *networking.Ingress) bool { return ing.Annotations["nginx.org/mergeable-ingress-type"] == "minion" } // isMaster determines is an ingress is a master or not -func isMaster(ing *v1beta1.Ingress) bool { +func isMaster(ing *networking.Ingress) bool { return ing.Annotations["nginx.org/mergeable-ingress-type"] == "master" } // hasChanges determines if current ingress has changes compared to old ingress -func hasChanges(old *v1beta1.Ingress, current *v1beta1.Ingress) bool { +func hasChanges(old *networking.Ingress, current *networking.Ingress) bool { old.Status.LoadBalancer.Ingress = current.Status.LoadBalancer.Ingress old.ResourceVersion = current.ResourceVersion return !reflect.DeepEqual(old, current) @@ -179,3 +183,20 @@ func ParseNamespaceName(value string) (ns string, name string, err error) { } return res[0], res[1], nil } + +// GetK8sVersion returns the running version of k8s +func GetK8sVersion(client kubernetes.Interface) (v *version.Version, err error) { + + serverVersion, err := client.Discovery().ServerVersion() + if err != nil { + return nil, err + } + + runningVersion, err := version.ParseGeneric(serverVersion.String()) + if err != nil { + return nil, fmt.Errorf("unexpected error parsing running Kubernetes version: %v", err) + } + glog.V(3).Infof("Kubernetes version: %v", runningVersion) + + return runningVersion, nil +} diff --git a/vendor/k8s.io/apimachinery/pkg/util/version/doc.go b/vendor/k8s.io/apimachinery/pkg/util/version/doc.go new file mode 100644 index 0000000000..5b2b22b6d0 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/version/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package version provides utilities for version number comparisons +package version // import "k8s.io/apimachinery/pkg/util/version" diff --git a/vendor/k8s.io/apimachinery/pkg/util/version/version.go b/vendor/k8s.io/apimachinery/pkg/util/version/version.go new file mode 100644 index 0000000000..8946926aac --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/version/version.go @@ -0,0 +1,322 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package version + +import ( + "bytes" + "fmt" + "regexp" + "strconv" + "strings" +) + +// Version is an opqaue representation of a version number +type Version struct { + components []uint + semver bool + preRelease string + buildMetadata string +} + +var ( + // versionMatchRE splits a version string into numeric and "extra" parts + versionMatchRE = regexp.MustCompile(`^\s*v?([0-9]+(?:\.[0-9]+)*)(.*)*$`) + // extraMatchRE splits the "extra" part of versionMatchRE into semver pre-release and build metadata; it does not validate the "no leading zeroes" constraint for pre-release + extraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\s*$`) +) + +func parse(str string, semver bool) (*Version, error) { + parts := versionMatchRE.FindStringSubmatch(str) + if parts == nil { + return nil, fmt.Errorf("could not parse %q as version", str) + } + numbers, extra := parts[1], parts[2] + + components := strings.Split(numbers, ".") + if (semver && len(components) != 3) || (!semver && len(components) < 2) { + return nil, fmt.Errorf("illegal version string %q", str) + } + + v := &Version{ + components: make([]uint, len(components)), + semver: semver, + } + for i, comp := range components { + if (i == 0 || semver) && strings.HasPrefix(comp, "0") && comp != "0" { + return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) + } + num, err := strconv.ParseUint(comp, 10, 0) + if err != nil { + return nil, fmt.Errorf("illegal non-numeric version component %q in %q: %v", comp, str, err) + } + v.components[i] = uint(num) + } + + if semver && extra != "" { + extraParts := extraMatchRE.FindStringSubmatch(extra) + if extraParts == nil { + return nil, fmt.Errorf("could not parse pre-release/metadata (%s) in version %q", extra, str) + } + v.preRelease, v.buildMetadata = extraParts[1], extraParts[2] + + for _, comp := range strings.Split(v.preRelease, ".") { + if _, err := strconv.ParseUint(comp, 10, 0); err == nil { + if strings.HasPrefix(comp, "0") && comp != "0" { + return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) + } + } + } + } + + return v, nil +} + +// ParseGeneric parses a "generic" version string. The version string must consist of two +// or more dot-separated numeric fields (the first of which can't have leading zeroes), +// followed by arbitrary uninterpreted data (which need not be separated from the final +// numeric field by punctuation). For convenience, leading and trailing whitespace is +// ignored, and the version can be preceded by the letter "v". See also ParseSemantic. +func ParseGeneric(str string) (*Version, error) { + return parse(str, false) +} + +// MustParseGeneric is like ParseGeneric except that it panics on error +func MustParseGeneric(str string) *Version { + v, err := ParseGeneric(str) + if err != nil { + panic(err) + } + return v +} + +// ParseSemantic parses a version string that exactly obeys the syntax and semantics of +// the "Semantic Versioning" specification (http://semver.org/) (although it ignores +// leading and trailing whitespace, and allows the version to be preceded by "v"). For +// version strings that are not guaranteed to obey the Semantic Versioning syntax, use +// ParseGeneric. +func ParseSemantic(str string) (*Version, error) { + return parse(str, true) +} + +// MustParseSemantic is like ParseSemantic except that it panics on error +func MustParseSemantic(str string) *Version { + v, err := ParseSemantic(str) + if err != nil { + panic(err) + } + return v +} + +// Major returns the major release number +func (v *Version) Major() uint { + return v.components[0] +} + +// Minor returns the minor release number +func (v *Version) Minor() uint { + return v.components[1] +} + +// Patch returns the patch release number if v is a Semantic Version, or 0 +func (v *Version) Patch() uint { + if len(v.components) < 3 { + return 0 + } + return v.components[2] +} + +// BuildMetadata returns the build metadata, if v is a Semantic Version, or "" +func (v *Version) BuildMetadata() string { + return v.buildMetadata +} + +// PreRelease returns the prerelease metadata, if v is a Semantic Version, or "" +func (v *Version) PreRelease() string { + return v.preRelease +} + +// Components returns the version number components +func (v *Version) Components() []uint { + return v.components +} + +// WithMajor returns copy of the version object with requested major number +func (v *Version) WithMajor(major uint) *Version { + result := *v + result.components = []uint{major, v.Minor(), v.Patch()} + return &result +} + +// WithMinor returns copy of the version object with requested minor number +func (v *Version) WithMinor(minor uint) *Version { + result := *v + result.components = []uint{v.Major(), minor, v.Patch()} + return &result +} + +// WithPatch returns copy of the version object with requested patch number +func (v *Version) WithPatch(patch uint) *Version { + result := *v + result.components = []uint{v.Major(), v.Minor(), patch} + return &result +} + +// WithPreRelease returns copy of the version object with requested prerelease +func (v *Version) WithPreRelease(preRelease string) *Version { + result := *v + result.components = []uint{v.Major(), v.Minor(), v.Patch()} + result.preRelease = preRelease + return &result +} + +// WithBuildMetadata returns copy of the version object with requested buildMetadata +func (v *Version) WithBuildMetadata(buildMetadata string) *Version { + result := *v + result.components = []uint{v.Major(), v.Minor(), v.Patch()} + result.buildMetadata = buildMetadata + return &result +} + +// String converts a Version back to a string; note that for versions parsed with +// ParseGeneric, this will not include the trailing uninterpreted portion of the version +// number. +func (v *Version) String() string { + var buffer bytes.Buffer + + for i, comp := range v.components { + if i > 0 { + buffer.WriteString(".") + } + buffer.WriteString(fmt.Sprintf("%d", comp)) + } + if v.preRelease != "" { + buffer.WriteString("-") + buffer.WriteString(v.preRelease) + } + if v.buildMetadata != "" { + buffer.WriteString("+") + buffer.WriteString(v.buildMetadata) + } + + return buffer.String() +} + +// compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0 +// if they are equal +func (v *Version) compareInternal(other *Version) int { + + vLen := len(v.components) + oLen := len(other.components) + for i := 0; i < vLen && i < oLen; i++ { + switch { + case other.components[i] < v.components[i]: + return 1 + case other.components[i] > v.components[i]: + return -1 + } + } + + // If components are common but one has more items and they are not zeros, it is bigger + switch { + case oLen < vLen && !onlyZeros(v.components[oLen:]): + return 1 + case oLen > vLen && !onlyZeros(other.components[vLen:]): + return -1 + } + + if !v.semver || !other.semver { + return 0 + } + + switch { + case v.preRelease == "" && other.preRelease != "": + return 1 + case v.preRelease != "" && other.preRelease == "": + return -1 + case v.preRelease == other.preRelease: // includes case where both are "" + return 0 + } + + vPR := strings.Split(v.preRelease, ".") + oPR := strings.Split(other.preRelease, ".") + for i := 0; i < len(vPR) && i < len(oPR); i++ { + vNum, err := strconv.ParseUint(vPR[i], 10, 0) + if err == nil { + oNum, err := strconv.ParseUint(oPR[i], 10, 0) + if err == nil { + switch { + case oNum < vNum: + return 1 + case oNum > vNum: + return -1 + default: + continue + } + } + } + if oPR[i] < vPR[i] { + return 1 + } else if oPR[i] > vPR[i] { + return -1 + } + } + + switch { + case len(oPR) < len(vPR): + return 1 + case len(oPR) > len(vPR): + return -1 + } + + return 0 +} + +// returns false if array contain any non-zero element +func onlyZeros(array []uint) bool { + for _, num := range array { + if num != 0 { + return false + } + } + return true +} + +// AtLeast tests if a version is at least equal to a given minimum version. If both +// Versions are Semantic Versions, this will use the Semantic Version comparison +// algorithm. Otherwise, it will compare only the numeric components, with non-present +// components being considered "0" (ie, "1.4" is equal to "1.4.0"). +func (v *Version) AtLeast(min *Version) bool { + return v.compareInternal(min) != -1 +} + +// LessThan tests if a version is less than a given version. (It is exactly the opposite +// of AtLeast, for situations where asking "is v too old?" makes more sense than asking +// "is v new enough?".) +func (v *Version) LessThan(other *Version) bool { + return v.compareInternal(other) == -1 +} + +// Compare compares v against a version string (which will be parsed as either Semantic +// or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if +// it is greater than other, or 0 if they are equal. +func (v *Version) Compare(other string) (int, error) { + ov, err := parse(other, v.semver) + if err != nil { + return 0, err + } + return v.compareInternal(ov), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a5133aac85..dc44696458 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -290,6 +290,7 @@ k8s.io/apimachinery/pkg/util/sets k8s.io/apimachinery/pkg/util/strategicpatch k8s.io/apimachinery/pkg/util/validation k8s.io/apimachinery/pkg/util/validation/field +k8s.io/apimachinery/pkg/util/version k8s.io/apimachinery/pkg/util/wait k8s.io/apimachinery/pkg/util/yaml k8s.io/apimachinery/pkg/version From 4dca62107574311a3e12ce78106c473237243625 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Wed, 9 Sep 2020 08:49:59 -0700 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Dean Coakley --- docs-web/installation/installation-with-manifests.md | 2 +- internal/k8s/utils.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs-web/installation/installation-with-manifests.md b/docs-web/installation/installation-with-manifests.md index 9903c38b20..9e01a94210 100644 --- a/docs-web/installation/installation-with-manifests.md +++ b/docs-web/installation/installation-with-manifests.md @@ -49,7 +49,7 @@ In this section, we create resources common for most of the Ingress Controller i 1. Create an IngressClass resource (for Kubernetes >= 1.18): ``` - $ kubectl apply -f common/ingress-class.yaml + $ kubectl apply -f common/ingress-class.yaml ``` If you would like to set the Ingress Controller as the default ingress, uncomment the annotation `ingressclass.kubernetes.io/is-default-class`. With this annotation set to true all the new Ingresses without an ingressClassName field specified will be assigned this IngressClass. diff --git a/internal/k8s/utils.go b/internal/k8s/utils.go index 98d8e8e34c..baea7770ce 100644 --- a/internal/k8s/utils.go +++ b/internal/k8s/utils.go @@ -186,7 +186,6 @@ func ParseNamespaceName(value string) (ns string, name string, err error) { // GetK8sVersion returns the running version of k8s func GetK8sVersion(client kubernetes.Interface) (v *version.Version, err error) { - serverVersion, err := client.Discovery().ServerVersion() if err != nil { return nil, err From 84d275368bc80c859b3127bf03430a49fe8c4fdf Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Wed, 9 Sep 2020 08:58:22 -0700 Subject: [PATCH 3/6] Add min supported version in helm chart --- deployments/helm-chart/Chart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/deployments/helm-chart/Chart.yaml b/deployments/helm-chart/Chart.yaml index 70f53a99a0..a4cd15d3d0 100644 --- a/deployments/helm-chart/Chart.yaml +++ b/deployments/helm-chart/Chart.yaml @@ -2,6 +2,7 @@ name: nginx-ingress version: edge appVersion: edge apiVersion: v1 +kubeVersion: ">= 1.15.0" description: NGINX Ingress Controller icon: https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/master/deployments/helm-chart/chart-icon.png sources: From 379535f7a3abafcfdf881512c99e70a9961a2e43 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Wed, 9 Sep 2020 15:57:16 -0700 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Michael Pleshakov --- docs-web/installation/installation-with-manifests.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-web/installation/installation-with-manifests.md b/docs-web/installation/installation-with-manifests.md index 9e01a94210..2e7ab8246c 100644 --- a/docs-web/installation/installation-with-manifests.md +++ b/docs-web/installation/installation-with-manifests.md @@ -51,9 +51,9 @@ In this section, we create resources common for most of the Ingress Controller i ``` $ kubectl apply -f common/ingress-class.yaml ``` - If you would like to set the Ingress Controller as the default ingress, uncomment the annotation `ingressclass.kubernetes.io/is-default-class`. With this annotation set to true all the new Ingresses without an ingressClassName field specified will be assigned this IngressClass. + If you would like to set the Ingress Controller as the default one, uncomment the annotation `ingressclass.kubernetes.io/is-default-class`. With this annotation set to true all the new Ingresses without an ingressClassName field specified will be assigned this IngressClass. - ***Note**: The Ingress Controller will fail to start without an IngressClass resource. + **Note**: The Ingress Controller will fail to start without an IngressClass resource. ### Create Custom Resources From aea6810353f8adbc258725afd8832d8cd0bec6d7 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Wed, 9 Sep 2020 15:54:30 -0700 Subject: [PATCH 5/6] PR feedback --- cmd/nginx-ingress/main.go | 2 +- deployments/helm-chart/Chart.yaml | 2 +- .../command-line-arguments.md | 4 +++- .../ingress-resources/basic-configuration.md | 17 ++++++++++++----- docs-web/installation/installation-with-helm.md | 10 +--------- .../running-multiple-ingress-controllers.md | 7 +++++-- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index e70003b951..d63af89e4a 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -288,7 +288,7 @@ func main() { glog.Fatalf("Error when getting IngressClass %v: %v", *ingressClass, err) } - if ingressClassRes != nil && ingressClassRes.Spec.Controller != k8s.IngressControllerName { + if ingressClassRes.Spec.Controller != k8s.IngressControllerName { glog.Fatalf("IngressClass with name %v has an invalid Spec.Controller %v", ingressClassRes.Name, ingressClassRes.Spec.Controller) } } diff --git a/deployments/helm-chart/Chart.yaml b/deployments/helm-chart/Chart.yaml index a4cd15d3d0..8e3b46d386 100644 --- a/deployments/helm-chart/Chart.yaml +++ b/deployments/helm-chart/Chart.yaml @@ -2,7 +2,7 @@ name: nginx-ingress version: edge appVersion: edge apiVersion: v1 -kubeVersion: ">= 1.15.0" +kubeVersion: ">= 1.14.0" description: NGINX Ingress Controller icon: https://raw.githubusercontent.com/nginxinc/kubernetes-ingress/master/deployments/helm-chart/chart-icon.png sources: diff --git a/docs-web/configuration/global-configuration/command-line-arguments.md b/docs-web/configuration/global-configuration/command-line-arguments.md index f2574ad05f..31f7417311 100644 --- a/docs-web/configuration/global-configuration/command-line-arguments.md +++ b/docs-web/configuration/global-configuration/command-line-arguments.md @@ -72,10 +72,12 @@ Below we describe the available command-line arguments: The Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class - i.e have the annotation "kubernetes.io/ingress.class" (for Ingress resources) or field "ingressClassName" (for VirtualServer/VirtualServerRoute resources) equal to the class. - Additionally, the Ingress Controller processes resources that do not have the class set, which can be disabled by setting the "-use-ingress-class-only" flag (default "nginx"). + Additionally, the Ingress Controller processes resources that do not have the class set, which can be disabled by setting the "-use-ingress-class-only" flag. The Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the "ingressClassName" field. + (default "nginx") + .. option:: -ingress-template-path Path to the ingress NGINX configuration template for an ingress resource. Default for NGINX is "nginx.ingress.tmpl"; default for NGINX Plus is "nginx-plus.ingress.tmpl". diff --git a/docs-web/configuration/ingress-resources/basic-configuration.md b/docs-web/configuration/ingress-resources/basic-configuration.md index 1126708042..62d9a462f5 100644 --- a/docs-web/configuration/ingress-resources/basic-configuration.md +++ b/docs-web/configuration/ingress-resources/basic-configuration.md @@ -63,16 +63,23 @@ Starting from Kubernetes 1.18, you can use the following new features: serviceName: coffee-svc servicePort: 80 ``` -* The `ingressClassName` field is now supported. When using this filed you need to create the `IngressClass` resource with the corresponding `name`, for example create the following resource: +* The `ingressClassName` field is now supported: ```yaml apiVersion: networking.k8s.io/v1beta1 - kind: IngressClass + kind: Ingress metadata: - name: nginx + name: cafe-ingress spec: - controller: nginx.org/ingress-controller + ingressClassName: nginx + tls: + - hosts: + - cafe.example.com + secretName: cafe-secret + rules: + - host: cafe.example.com + . . . ``` - and then set `spec.IngressClassName` to `nginx` in the `Ingress` resource. + When using this filed you need to create the `IngressClass` resource with the corresponding `name`. See Step 3 *Create an IngressClass resource* of the [Create Common Resources](https://deploy-preview-1133--nginx-kubernetes-ingress.netlify.app/nginx-ingress-controller/installation/installation-with-manifests/#create-common-resources) section. ## Restrictions diff --git a/docs-web/installation/installation-with-helm.md b/docs-web/installation/installation-with-helm.md index 3fbb8800d4..192d11674a 100644 --- a/docs-web/installation/installation-with-helm.md +++ b/docs-web/installation/installation-with-helm.md @@ -274,15 +274,7 @@ The following tables lists the configurable parameters of the NGINX Ingress cont - The number of replicas of the Ingress controller deployment. - 1 * - ``controller.ingressClass`` - - A class of the Ingress controller. - For Kubernetes >= 1.18, a corresponding IngressClass resource with the name equal to the class must be deployed. Otherwise, - the Ingress Controller will fail to start. - The Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class. - - For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class - i.e have the annotation "kubernetes.io/ingress.class" equal to the class. - Additionally, the Ingress Controller processes resources that do not have the class set, which can be disabled by setting the "-use-ingress-class-only" flag. - - The Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the "ingressClassName" field for all versions of kubernetes. + - A class of the Ingress controller. For Kubernetes >= 1.18, the Ingress controller only processes resources that belong to its class - i.e. have the ``"ingressClassName"`` field resource equal to the class. For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class - i.e have the annotation ``"kubernetes.io/ingress.class"`` equal to the class. Additionally, the Ingress Controller processes resources that do not have the class set, which can be disabled by setting the ``controller.useIngressClassOnly`` flag. The Ingress Controller processes all the VirtualServer/VirtualServerRoute resources that do not have the ``"ingressClassName"`` field for all versions of kubernetes. - nginx * - ``controller.useIngressClassOnly`` - Ignore Ingress resources without the ``"kubernetes.io/ingress.class"`` annotation. For kubernetes versions >= 1.18 this flag will be IGNORED. diff --git a/docs-web/installation/running-multiple-ingress-controllers.md b/docs-web/installation/running-multiple-ingress-controllers.md index 4048dfa69d..8d4c5a2dc6 100644 --- a/docs-web/installation/running-multiple-ingress-controllers.md +++ b/docs-web/installation/running-multiple-ingress-controllers.md @@ -11,7 +11,7 @@ This document explains the following topics: The smooth coexistence of multiple Ingress Controllers in one cluster is provided by the Ingress class concept, which mandates the following: * Every Ingress Controller must only handle Ingress resources for its particular class. -* Ingress resources should be annotated with the `kubernetes.io/ingress.class` annotation set to the value, which corresponds to the class of the Ingress Controller the user wants to use. +* For Kubernetes < 1.18, Ingress resources should be annotated with the `kubernetes.io/ingress.class` annotation set to the value, which corresponds to the class of the Ingress Controller the user wants to use. * When using versions of Kubernetes >= 1.18, Ingress resources should have the `ingressClassName` field set to the value, which corresponds to the class of the Ingress Controller the user wants to use. * VirtualServer and VirtualServerRoute resources should have the `ingressClassName` field set to the value, which corresponds to the class of the Ingress Controller the user wants to use. @@ -19,7 +19,10 @@ The smooth coexistence of multiple Ingress Controllers in one cluster is provide The default Ingress class of NGINX Ingress Controller is `nginx`, which means that it only handles configuration resources with the `class` set to `nginx`. You can customize the class through the `-ingress-class` command-line argument. -**Note**: By default, if the `class` is not set in a configuration resource, the Ingress Controller will handle the resource. This is controlled via the `-use-ingress-class-only` argument. +**Notes**: +* For Kubernetes < 1.18, if the class is not set in an Ingress configuration resource, the Ingress Controller will handle the resource. This is controlled via the `-use-ingress-class-only` argument. +* For Kubernetes >= 1.18, if the class is not set in an Ingress resource, Kubernetes will set it to the class of the default Ingress Controller. To make the Ingress Controller the default one, the `ingressclass.kubernetes.io/is-default-class` must be set on the IngressClass resource. See Step 3 *Create an IngressClass resource* of the [Create Common Resources](https://deploy-preview-1133--nginx-kubernetes-ingress.netlify.app/nginx-ingress-controller/installation/installation-with-manifests/#create-common-resources) section. +* For VirtualServer and VirtualServerRoute resources, the Ingress Controller will always handle resources with an empty class. ## Running NGINX Ingress Controller and Another Ingress Controller From 27bb3405d80de009f06a521aa2b1cb33fb194ab5 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Thu, 10 Sep 2020 10:36:30 -0700 Subject: [PATCH 6/6] fix links --- docs-web/configuration/ingress-resources/basic-configuration.md | 2 +- docs-web/installation/running-multiple-ingress-controllers.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-web/configuration/ingress-resources/basic-configuration.md b/docs-web/configuration/ingress-resources/basic-configuration.md index 62d9a462f5..9ca9c443a6 100644 --- a/docs-web/configuration/ingress-resources/basic-configuration.md +++ b/docs-web/configuration/ingress-resources/basic-configuration.md @@ -79,7 +79,7 @@ Starting from Kubernetes 1.18, you can use the following new features: - host: cafe.example.com . . . ``` - When using this filed you need to create the `IngressClass` resource with the corresponding `name`. See Step 3 *Create an IngressClass resource* of the [Create Common Resources](https://deploy-preview-1133--nginx-kubernetes-ingress.netlify.app/nginx-ingress-controller/installation/installation-with-manifests/#create-common-resources) section. + When using this filed you need to create the `IngressClass` resource with the corresponding `name`. See Step 3 *Create an IngressClass resource* of the [Create Common Resources](/nginx-ingress-controller/installation/installation-with-manifests/#create-common-resources) section. ## Restrictions diff --git a/docs-web/installation/running-multiple-ingress-controllers.md b/docs-web/installation/running-multiple-ingress-controllers.md index 8d4c5a2dc6..23da0e4d98 100644 --- a/docs-web/installation/running-multiple-ingress-controllers.md +++ b/docs-web/installation/running-multiple-ingress-controllers.md @@ -21,7 +21,7 @@ The default Ingress class of NGINX Ingress Controller is `nginx`, which means th **Notes**: * For Kubernetes < 1.18, if the class is not set in an Ingress configuration resource, the Ingress Controller will handle the resource. This is controlled via the `-use-ingress-class-only` argument. -* For Kubernetes >= 1.18, if the class is not set in an Ingress resource, Kubernetes will set it to the class of the default Ingress Controller. To make the Ingress Controller the default one, the `ingressclass.kubernetes.io/is-default-class` must be set on the IngressClass resource. See Step 3 *Create an IngressClass resource* of the [Create Common Resources](https://deploy-preview-1133--nginx-kubernetes-ingress.netlify.app/nginx-ingress-controller/installation/installation-with-manifests/#create-common-resources) section. +* For Kubernetes >= 1.18, if the class is not set in an Ingress resource, Kubernetes will set it to the class of the default Ingress Controller. To make the Ingress Controller the default one, the `ingressclass.kubernetes.io/is-default-class` must be set on the IngressClass resource. See Step 3 *Create an IngressClass resource* of the [Create Common Resources](/nginx-ingress-controller/installation/installation-with-manifests/#create-common-resources) section. * For VirtualServer and VirtualServerRoute resources, the Ingress Controller will always handle resources with an empty class. ## Running NGINX Ingress Controller and Another Ingress Controller