From 3012937b34879fdab56a2f1f43d930243d090c7e Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 22 Apr 2024 15:19:05 -0400 Subject: [PATCH] change the config-gateway format (#701) * change the config-gateway format * fix verify script * fix linting and extra fmt.Println * fix contour config * fix duplicate import * fix e2e test configs * fix gateway config tests --- config/config-gateway.yaml | 38 ++-- hack/verify-codegen.sh | 2 +- pkg/reconciler/ingress/config/gateway.go | 188 ++++++++++-------- pkg/reconciler/ingress/config/gateway_test.go | 126 ++++++++++-- pkg/reconciler/ingress/config/store.go | 10 +- .../config/testdata/config-gateway.yaml | 54 +---- .../config/testdata/config-network.yaml | 170 ---------------- .../ingress/config/zz_generated.deepcopy.go | 46 +++-- pkg/reconciler/ingress/controller.go | 2 +- pkg/reconciler/ingress/ingress.go | 34 ++-- pkg/reconciler/ingress/ingress_test.go | 38 ++-- pkg/reconciler/ingress/lister.go | 16 +- pkg/reconciler/ingress/reconcile_resources.go | 9 +- pkg/reconciler/ingress/resources/httproute.go | 15 +- .../ingress/resources/httproute_test.go | 19 +- test/e2e/gateway_config_test.go | 4 +- test/e2e/testdata/contour-no-service-vis.yaml | 9 +- test/e2e/testdata/istio-no-service-vis.yaml | 9 +- third_party/contour/config-gateway.yaml | 11 +- 19 files changed, 341 insertions(+), 459 deletions(-) mode change 100644 => 120000 pkg/reconciler/ingress/config/testdata/config-gateway.yaml delete mode 100644 pkg/reconciler/ingress/config/testdata/config-network.yaml diff --git a/config/config-gateway.yaml b/config/config-gateway.yaml index a77ae3047..47d133807 100644 --- a/config/config-gateway.yaml +++ b/config/config-gateway.yaml @@ -30,29 +30,29 @@ data: # # ################################ - # ***** WARNING ***** - # This configuration is tentative. - # The structure might be changed in the future version. - # ******************** + # This block is not actually functional configuration, + # but serves to illustrate the available configuration + # options and document them in a way that is accessible + # to users that `kubectl edit` this config map. # - # visibility: | - # : | - # gatewayClass: GatewayClass Name - # gateway: the namespace/name of Gateway - # service: (optional) the namespace/name of Service for the Gateway - # - # If a .service is not provided, + # These sample configuration options may be copied out of + # this example block and unindented to be in the data block + # to actually change the configuration. + + # When configuring Gateways below if 'service' is not provided, # net-gateway-api will use the first address on the Gateway status - # for probing. This is useful when the Gateway is off cluster. - # See: https://github.com/knative-extensions/net-gateway-api/issues/665 + # for probing. This is useful when the Gateway proxy is off cluster. # - # The gateway configuration for the default visibility. - visibility: | - ExternalIP: - class: istio + # See: https://github.com/knative-extensions/net-gateway-api/issues/665 + + # external-gateways defines the Gateway to be used for external traffic + external-gateways: | + - class: istio gateway: istio-system/knative-gateway service: istio-system/istio-ingressgateway - ClusterLocal: - class: istio + + # local-gateways defines the Gateway to be used for cluster local traffic + local-gateways: | + - class: istio gateway: istio-system/knative-local-gateway service: istio-system/knative-local-gateway diff --git a/hack/verify-codegen.sh b/hack/verify-codegen.sh index 5bd9c45a6..fc568f8ee 100755 --- a/hack/verify-codegen.sh +++ b/hack/verify-codegen.sh @@ -38,7 +38,7 @@ cp -aR "${REPO_ROOT_DIR}/go.sum" "${REPO_ROOT_DIR}/pkg" "${REPO_ROOT_DIR}/vendor "${REPO_ROOT_DIR}/hack/update-codegen.sh" echo "Diffing ${REPO_ROOT_DIR} against freshly generated codegen" ret=0 -diff -Naupr "${REPO_ROOT_DIR}/pkg" "${TMP_DIFFROOT}/pkg" || ret=1 +diff -Naupr --no-dereference "${REPO_ROOT_DIR}/pkg" "${TMP_DIFFROOT}/pkg" || ret=1 diff -Naupr --no-dereference "${REPO_ROOT_DIR}/vendor" "${TMP_DIFFROOT}/vendor" || ret=1 # Restore working tree state diff --git a/pkg/reconciler/ingress/config/gateway.go b/pkg/reconciler/ingress/config/gateway.go index d0debf696..8f5adaffe 100644 --- a/pkg/reconciler/ingress/config/gateway.go +++ b/pkg/reconciler/ingress/config/gateway.go @@ -18,13 +18,12 @@ package config import ( "fmt" + "strings" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/cache" + "knative.dev/pkg/configmap" "sigs.k8s.io/yaml" - - "knative.dev/networking/pkg/apis/networking/v1alpha1" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -33,114 +32,127 @@ const ( // GatewayConfigName is the config map name for the gateway configuration. GatewayConfigName = "config-gateway" - visibilityConfigKey = "visibility" - - // defaultGatewayClass is the gatewayclass name for the gateway. - defaultGatewayClass = "istio" + externalGatewaysKey = "external-gateways" + localGatewaysKey = "local-gateways" ) -var ( - // defaultIstioGateway is the default gateway. - defaultIstioGateway = &types.NamespacedName{Namespace: "istio-system", Name: "knative-gateway"} - - // defaultIstioLocalGateway is the default local gateway: - defaultIstioLocalGateway = &types.NamespacedName{Namespace: "istio-system", Name: "knative-local-gateway"} +func defaultExternalGateways() []Gateway { + return []Gateway{{ + NamespacedName: types.NamespacedName{ + Name: "knative-gateway", + Namespace: "istio-system", + }, + + Class: "istio", + Service: &types.NamespacedName{ + Name: "istio-ingressgateway", + Namespace: "istio-system", + }, + }} +} - // defaultLocalGatewayService holds the default local gateway service. - defaultLocalGatewayService = &types.NamespacedName{Namespace: "istio-system", Name: "knative-local-gateway"} +func defaultLocalGateways() []Gateway { + return []Gateway{{ + NamespacedName: types.NamespacedName{ + Name: "knative-local-gateway", + Namespace: "istio-system", + }, + Class: "istio", + Service: &types.NamespacedName{ + Name: "knative-local-gateway", + Namespace: "istio-system", + }, + }} +} - // defaultGatewayService is the default gateway service. - defaultGatewayService = &types.NamespacedName{Namespace: "istio-system", Name: "istio-ingressgateway"} -) +// GatewayPlugin specifies which Gateways are used for external/local traffic +type GatewayPlugin struct { + ExternalGateways []Gateway + LocalGateways []Gateway +} -type GatewayConfig struct { - GatewayClass string - Gateway *types.NamespacedName - Service *types.NamespacedName +func (g *GatewayPlugin) ExternalGateway() Gateway { + return g.ExternalGateways[0] } -type visibilityValue struct { - GatewayClass string `json:"class,omitempty"` - Gateway string `json:"gateway,omitempty"` - Service string `json:"service,omitempty"` +func (g *GatewayPlugin) LocalGateway() Gateway { + return g.LocalGateways[0] } -// Gateway maps gateways to routes by matching the gateway's -// label selectors to the route's labels. type Gateway struct { - // Gateways map from gateway to label selector. If a route has - // labels matching a particular selector, it will use the - // corresponding gateway. If multiple selectors match, we choose - // the most specific selector. - Gateways map[v1alpha1.IngressVisibility]GatewayConfig -} + types.NamespacedName -// NewGatewayFromConfigMap creates a Gateway from the supplied ConfigMap -func NewGatewayFromConfigMap(configMap *corev1.ConfigMap) (*Gateway, error) { - v, ok := configMap.Data[visibilityConfigKey] - if !ok { - // These are the defaults. - return &Gateway{ - Gateways: map[v1alpha1.IngressVisibility]GatewayConfig{ - v1alpha1.IngressVisibilityExternalIP: {GatewayClass: defaultGatewayClass, Gateway: defaultIstioGateway, Service: defaultGatewayService}, - v1alpha1.IngressVisibilityClusterLocal: {GatewayClass: defaultGatewayClass, Gateway: defaultIstioLocalGateway, Service: defaultLocalGatewayService}, - }, - }, nil - } + Class string + Service *types.NamespacedName +} - visConfig := make(map[v1alpha1.IngressVisibility]visibilityValue) - if err := yaml.Unmarshal([]byte(v), &visConfig); err != nil { - return nil, err - } +// FromConfigMap creates a GatewayPlugin config from the supplied ConfigMap +func FromConfigMap(cm *corev1.ConfigMap) (*GatewayPlugin, error) { + var ( + err error + config = &GatewayPlugin{} + ) - for _, vis := range []v1alpha1.IngressVisibility{ - v1alpha1.IngressVisibilityClusterLocal, - v1alpha1.IngressVisibilityExternalIP, - } { - if _, ok := visConfig[vis]; !ok { - return nil, fmt.Errorf("visibility %q must not be empty", vis) + if data, ok := cm.Data[externalGatewaysKey]; ok { + config.ExternalGateways, err = parseGatewayConfig(data) + if err != nil { + return nil, fmt.Errorf("unable to parse %q: %w", externalGatewaysKey, err) } } - entry := make(map[v1alpha1.IngressVisibility]GatewayConfig) - for key, value := range visConfig { - // Check that the visibility makes sense. - switch key { - case v1alpha1.IngressVisibilityClusterLocal, v1alpha1.IngressVisibilityExternalIP: - default: - return nil, fmt.Errorf("unrecognized visibility: %q", key) - } - if value.GatewayClass == "" { - return nil, fmt.Errorf("visibility %q must set gatewayclass", key) - } - gateway, err := parseNamespacedName(value.Gateway) + if data, ok := cm.Data[localGatewaysKey]; ok { + config.LocalGateways, err = parseGatewayConfig(data) if err != nil { - return nil, fmt.Errorf("visibility %q failed to parse gateway: %w", key, err) + return nil, fmt.Errorf("unable to parse %q: %w", localGatewaysKey, err) } + } - var service *types.NamespacedName - if value.Service != "" { - service, err = parseNamespacedName(value.Service) - if err != nil { - return nil, fmt.Errorf("visibility %q failed to parse service: %w", key, err) - } - } + switch len(config.ExternalGateways) { + case 0: + config.ExternalGateways = defaultExternalGateways() + case 1: + default: + return nil, fmt.Errorf("only a single external gateway is supported") + } - entry[key] = GatewayConfig{ - GatewayClass: value.GatewayClass, - Gateway: gateway, - Service: service, - } + switch len(config.LocalGateways) { + case 0: + config.LocalGateways = defaultLocalGateways() + case 1: + default: + return nil, fmt.Errorf("only a single local gateway is supported") } - return &Gateway{Gateways: entry}, nil + + return config, nil } -func parseNamespacedName(namespacedName string) (*types.NamespacedName, error) { - namespace, name, err := cache.SplitMetaNamespaceKey(namespacedName) - if err != nil { +func parseGatewayConfig(data string) ([]Gateway, error) { + var entries []map[string]string + + if err := yaml.Unmarshal([]byte(data), &entries); err != nil { return nil, err - } else if namespace == "" || name == "" { - return nil, fmt.Errorf("missing namespace or name in %q", namespacedName) } - return &types.NamespacedName{Namespace: namespace, Name: name}, nil + + gws := make([]Gateway, 0, len(entries)) + + for i, entry := range entries { + gw := Gateway{} + + err := configmap.Parse(entry, + configmap.AsString("class", &gw.Class), + configmap.AsNamespacedName("gateway", &gw.NamespacedName), + configmap.AsOptionalNamespacedName("service", &gw.Service), + ) + if err != nil { + return nil, err + } + + if len(strings.TrimSpace(gw.Class)) == 0 { + return nil, fmt.Errorf(`entry [%d] field "class" is required`, i) + } + + gws = append(gws, gw) + } + + return gws, nil } diff --git a/pkg/reconciler/ingress/config/gateway_test.go b/pkg/reconciler/ingress/config/gateway_test.go index eb77d4153..601c3192d 100644 --- a/pkg/reconciler/ingress/config/gateway_test.go +++ b/pkg/reconciler/ingress/config/gateway_test.go @@ -17,42 +17,126 @@ limitations under the License. package config import ( + "strings" "testing" + corev1 "k8s.io/api/core/v1" . "knative.dev/pkg/configmap/testing" ) -func TestGateway(t *testing.T) { +func TestFromConfigMap(t *testing.T) { cm, example := ConfigMapsFromTestFile(t, GatewayConfigName) - if _, err := NewGatewayFromConfigMap(cm); err != nil { - t.Error("NewContourFromConfigMap(actual) =", err) + if _, err := FromConfigMap(cm); err != nil { + t.Error("FromConfigMap(actual) =", err) } - if _, err := NewGatewayFromConfigMap(example); err != nil { - t.Error("NewContourFromConfigMap(example) =", err) + if _, err := FromConfigMap(example); err != nil { + t.Error("FromConfigMap(example) =", err) } } -var modifiedVisibilityNoService = ` - ExternalIP: - class: istio - gateway: istio-system/knative-gateway - ClusterLocal: - class: istio - gateway: istio-system/knative-local-gateway -` +func TestFromConfigMapErrors(t *testing.T) { + cases := []struct { + name string + data map[string]string + want string + }{{ + name: "external-gateways bad yaml", + data: map[string]string{ + "external-gateways": `{`, + }, + want: `unable to parse "external-gateways"`, + }, { + name: "local-gateways bad yaml", + data: map[string]string{ + "local-gateways": `{`, + }, + want: `unable to parse "local-gateways"`, + }, { + name: "external-gateways multiple entries", + data: map[string]string{ + "external-gateways": `[{"class":"boo"},{"class":"boo"}]`, + }, + want: `only a single external gateway is supported`, + }, { + name: "local-gateways multiple entries", + data: map[string]string{ + "local-gateways": `[{"class":"boo"},{"class":"boo"}]`, + }, + want: `only a single local gateway is supported`, + }, { + name: "missing gateway class", + data: map[string]string{ + "local-gateways": `[{"gateway": "namespace/name"}]`, + }, + want: `unable to parse "local-gateways": entry [0] field "class" is required`, + }, { + name: "missing gateway name", + data: map[string]string{ + "local-gateways": `[{"class": "class", "gateway": "namespace/"}]`, + }, + want: `unable to parse "local-gateways": failed to parse "gateway"`, + }, { + name: "missing gateway namespace", + data: map[string]string{ + "local-gateways": `[{"class": "class", "gateway": "/name"}]`, + }, + want: `unable to parse "local-gateways": failed to parse "gateway"`, + }, { + name: "bad gateway entry", + data: map[string]string{ + "local-gateways": `[{"class": "class", "gateway": "name"}]`, + }, + want: `unable to parse "local-gateways"`, + }, { + name: "missing service name", + data: map[string]string{ + "local-gateways": `[{"class": "class", "gateway": "ns/n", "service":"ns/"}]`, + }, + want: `unable to parse "local-gateways": failed to parse "service"`, + }, { + name: "missing service namespace", + data: map[string]string{ + "local-gateways": `[{"class": "class", "gateway": "ns/n", "service":"/name"}]`, + }, + want: `unable to parse "local-gateways": failed to parse "service"`, + }, { + name: "bad service entry", + data: map[string]string{ + "local-gateways": `[{"class": "class", "gateway": "ns/n", "service":"name"}]`, + }, + want: `unable to parse "local-gateways"`, + }} -func TestGatewayNoService(t *testing.T) { - cm, example := ConfigMapsFromTestFile(t, GatewayConfigName) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, got := FromConfigMap(&corev1.ConfigMap{Data: tc.data}) - cm.Data[visibilityConfigKey] = modifiedVisibilityNoService - - if _, err := NewGatewayFromConfigMap(cm); err != nil { - t.Error("NewContourFromConfigMap(actual) =", err) + if got == nil { + t.Fatal("Expected an error to occur") + } + if !strings.HasPrefix(got.Error(), tc.want) { + t.Errorf("Unexpected error message %q - wanted prefix %q", got, tc.want) + } + }) } - if _, err := NewGatewayFromConfigMap(example); err != nil { - t.Error("NewContourFromConfigMap(example) =", err) +} + +func TestGatewayNoService(t *testing.T) { + _, err := FromConfigMap(&corev1.ConfigMap{ + Data: map[string]string{ + "external-gateways": ` + - class: istio + gateway: istio-system/knative-gateway`, + "local-gateways": ` + - class: istio + gateway: istio-system/knative-local-gateway`, + }, + }) + + if err != nil { + t.Errorf("FromConfigMap(noService) = %v", err) } } diff --git a/pkg/reconciler/ingress/config/store.go b/pkg/reconciler/ingress/config/store.go index 8ef1735e5..1e84f0ad3 100644 --- a/pkg/reconciler/ingress/config/store.go +++ b/pkg/reconciler/ingress/config/store.go @@ -29,8 +29,8 @@ type cfgKey struct{} // Config is the configuration for the route reconciler. type Config struct { - Network *networkcfg.Config - Gateway *Gateway + Network *networkcfg.Config + GatewayPlugin *GatewayPlugin } // FromContext obtains a Config injected into the passed context. @@ -77,7 +77,7 @@ func NewStore(ctx context.Context, onAfterStore ...func(name string, value inter "gateway-api", logger, configmap.Constructors{ - GatewayConfigName: NewGatewayFromConfigMap, + GatewayConfigName: FromConfigMap, networkcfg.ConfigMapName: network.NewConfigFromConfigMap, }, onAfterStore..., @@ -95,8 +95,8 @@ func (s *Store) ToContext(ctx context.Context) context.Context { // Load creates a Config for this store. func (s *Store) Load() *Config { config := &Config{ - Gateway: s.UntypedLoad(GatewayConfigName).(*Gateway).DeepCopy(), - Network: s.UntypedLoad(networkcfg.ConfigMapName).(*networkcfg.Config).DeepCopy(), + GatewayPlugin: s.UntypedLoad(GatewayConfigName).(*GatewayPlugin).DeepCopy(), + Network: s.UntypedLoad(networkcfg.ConfigMapName).(*networkcfg.Config).DeepCopy(), } return config } diff --git a/pkg/reconciler/ingress/config/testdata/config-gateway.yaml b/pkg/reconciler/ingress/config/testdata/config-gateway.yaml deleted file mode 100644 index 93c4ea319..000000000 --- a/pkg/reconciler/ingress/config/testdata/config-gateway.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2021 The Knative 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 -# -# https://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. - -apiVersion: v1 -kind: ConfigMap -metadata: - name: config-gateway - namespace: knative-serving -data: - _example: | - ################################ - # # - # EXAMPLE CONFIGURATION # - # # - ################################ - - # ***** WARNING ***** - # This configuration is tentative. - # The structure might be changed in the future version. - # ******************** - # - # visibility: | - # : | - # gatewayClass: GatewayClass Name - # gateway: the namespace/name of Gateway - # service: (optional) the namespace/name of Service for the Gateway - # - # If a .service is not provided, - # net-gateway-api will use the first address on the Gateway status - # for probing. This is useful when the Gateway is off cluster. - # See: https://github.com/knative-extensions/net-gateway-api/issues/665 - # - # The gateway configuration for the default visibility. - visibility: | - ExternalIP: - class: istio - gateway: istio-system/knative-gateway - service: istio-system/istio-ingressgateway - ClusterLocal: - class: istio - gateway: istio-system/knative-local-gateway - service: istio-system/knative-local-gateway diff --git a/pkg/reconciler/ingress/config/testdata/config-gateway.yaml b/pkg/reconciler/ingress/config/testdata/config-gateway.yaml new file mode 120000 index 000000000..eff30ef4a --- /dev/null +++ b/pkg/reconciler/ingress/config/testdata/config-gateway.yaml @@ -0,0 +1 @@ +../../../../../config/config-gateway.yaml \ No newline at end of file diff --git a/pkg/reconciler/ingress/config/testdata/config-network.yaml b/pkg/reconciler/ingress/config/testdata/config-network.yaml deleted file mode 100644 index bb52a6dee..000000000 --- a/pkg/reconciler/ingress/config/testdata/config-network.yaml +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright 2018 The Knative 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 -# -# https://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. - -apiVersion: v1 -kind: ConfigMap -metadata: - name: config-network - namespace: knative-serving - annotations: - knative.dev/example-checksum: "6e2033e0" -data: - _example: | - ################################ - # # - # EXAMPLE CONFIGURATION # - # # - ################################ - - # This block is not actually functional configuration, - # but serves to illustrate the available configuration - # options and document them in a way that is accessible - # to users that `kubectl edit` this config map. - # - # These sample configuration options may be copied out of - # this example block and unindented to be in the data block - # to actually change the configuration. - - # ingress-class specifies the default ingress class - # to use when not dictated by Route annotation. - # - # If not specified, will use the Istio ingress. - # - # Note that changing the Ingress class of an existing Route - # will result in undefined behavior. Therefore it is best to only - # update this value during the setup of Knative, to avoid getting - # undefined behavior. - ingress-class: "istio.ingress.networking.knative.dev" - - # certificate-class specifies the default Certificate class - # to use when not dictated by Route annotation. - # - # If not specified, will use the Cert-Manager Certificate. - # - # Note that changing the Certificate class of an existing Route - # will result in undefined behavior. Therefore it is best to only - # update this value during the setup of Knative, to avoid getting - # undefined behavior. - certificate-class: "cert-manager.certificate.networking.knative.dev" - - # namespace-wildcard-cert-selector specifies a LabelSelector which - # determines which namespaces should have a wildcard certificate - # provisioned. - # - # Use an empty value to disable the feature (this is the default): - # namespace-wildcard-cert-selector: "" - # - # Use an empty object to enable for all namespaces - # namespace-wildcard-cert-selector: {} - # - # Useful labels include the "kubernetes.io/metadata.name" label to - # avoid provisioning a certifcate for the "kube-system" namespaces. - # Use the following selector to match pre-1.0 behavior of using - # "networking.knative.dev/disableWildcardCert" to exclude namespaces: - # - # matchExpressions: - # - key: "networking.knative.dev/disableWildcardCert" - # operator: "NotIn" - # values: ["true"] - namespace-wildcard-cert-selector: "" - - # domain-template specifies the golang text template string to use - # when constructing the Knative service's DNS name. The default - # value is "{{.Name}}.{{.Namespace}}.{{.Domain}}". - # - # Valid variables defined in the template include Name, Namespace, Domain, - # Labels, and Annotations. Name will be the result of the tagTemplate - # below, if a tag is specified for the route. - # - # Changing this value might be necessary when the extra levels in - # the domain name generated is problematic for wildcard certificates - # that only support a single level of domain name added to the - # certificate's domain. In those cases you might consider using a value - # of "{{.Name}}-{{.Namespace}}.{{.Domain}}", or removing the Namespace - # entirely from the template. When choosing a new value be thoughtful - # of the potential for conflicts - for example, when users choose to use - # characters such as `-` in their service, or namespace, names. - # {{.Annotations}} or {{.Labels}} can be used for any customization in the - # go template if needed. - # We strongly recommend keeping namespace part of the template to avoid - # domain name clashes: - # eg. '{{.Name}}-{{.Namespace}}.{{ index .Annotations "sub"}}.{{.Domain}}' - # and you have an annotation {"sub":"foo"}, then the generated template - # would be {Name}-{Namespace}.foo.{Domain} - domain-template: "{{.Name}}.{{.Namespace}}.{{.Domain}}" - - # tagTemplate specifies the golang text template string to use - # when constructing the DNS name for "tags" within the traffic blocks - # of Routes and Configuration. This is used in conjunction with the - # domainTemplate above to determine the full URL for the tag. - tag-template: "{{.Tag}}-{{.Name}}" - - # Controls whether TLS certificates are automatically provisioned and - # installed in the Knative ingress to terminate external TLS connection. - # 1. Enabled: enabling auto-TLS feature. - # 2. Disabled: disabling auto-TLS feature. - auto-tls: "Disabled" - - # Controls the behavior of the HTTP endpoint for the Knative ingress. - # It requires autoTLS to be enabled. - # 1. Enabled: The Knative ingress will be able to serve HTTP connection. - # 2. Redirected: The Knative ingress will send a 301 redirect for all - # http connections, asking the clients to use HTTPS. - # - # "Disabled" option is deprecated. - http-protocol: "Enabled" - - # rollout-duration contains the minimal duration in seconds over which the - # Configuration traffic targets are rolled out to the newest revision. - rollout-duration: "0" - - # autocreate-cluster-domain-claims controls whether ClusterDomainClaims should - # be automatically created (and deleted) as needed when DomainMappings are - # reconciled. - # - # If this is "false" (the default), the cluster administrator is - # responsible for creating ClusterDomainClaims and delegating them to - # namespaces via their spec.Namespace field. This setting should be used in - # multitenant environments which need to control which namespace can use a - # particular domain name in a domain mapping. - # - # If this is "true", users are able to associate arbitrary names with their - # services via the DomainMapping feature. - autocreate-cluster-domain-claims: "false" - - # If true, networking plugins can add additional information to deployed - # applications to make their pods directly accessible via their IPs even if mesh is - # enabled and thus direct-addressability is usually not possible. - # Consumers like Knative Serving can use this setting to adjust their behavior - # accordingly, i.e. to drop fallback solutions for non-pod-addressable systems. - # - # NOTE: This flag is in an alpha state and is mostly here to enable internal testing - # for now. Use with caution. - enable-mesh-pod-addressability: "false" - - # mesh-compatibility-mode indicates whether consumers of network plugins - # should directly contact Pod IPs (most efficient), or should use the - # Cluster IP (less efficient, needed when mesh is enabled unless - # `enable-mesh-pod-addressability`, above, is set). - # Permitted values are: - # - "auto" (default): automatically determine which mesh mode to use by trying Pod IP and falling back to Cluster IP as needed. - # - "enabled": always use Cluster IP and do not attempt to use Pod IPs. - # - "disabled": always use Pod IPs and do not fall back to Cluster IP on failure. - mesh-compatibility-mode: "auto" - - # Defines the scheme used for external URLs if autoTLS is not enabled. - # This can be used for making Knative report all URLs as "HTTPS" for example, if you're - # fronting Knative with an external loadbalancer that deals with TLS termination and - # Knative doesn't know about that otherwise. - default-external-scheme: "http" diff --git a/pkg/reconciler/ingress/config/zz_generated.deepcopy.go b/pkg/reconciler/ingress/config/zz_generated.deepcopy.go index 6f3d4beba..79cab0492 100644 --- a/pkg/reconciler/ingress/config/zz_generated.deepcopy.go +++ b/pkg/reconciler/ingress/config/zz_generated.deepcopy.go @@ -23,7 +23,6 @@ package config import ( types "k8s.io/apimachinery/pkg/types" - v1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1" pkgconfig "knative.dev/networking/pkg/config" ) @@ -35,9 +34,9 @@ func (in *Config) DeepCopyInto(out *Config) { *out = new(pkgconfig.Config) (*in).DeepCopyInto(*out) } - if in.Gateway != nil { - in, out := &in.Gateway, &out.Gateway - *out = new(Gateway) + if in.GatewayPlugin != nil { + in, out := &in.GatewayPlugin, &out.GatewayPlugin + *out = new(GatewayPlugin) (*in).DeepCopyInto(*out) } return @@ -56,12 +55,11 @@ func (in *Config) DeepCopy() *Config { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Gateway) DeepCopyInto(out *Gateway) { *out = *in - if in.Gateways != nil { - in, out := &in.Gateways, &out.Gateways - *out = make(map[v1alpha1.IngressVisibility]GatewayConfig, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() - } + out.NamespacedName = in.NamespacedName + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = new(types.NamespacedName) + **out = **in } return } @@ -77,27 +75,31 @@ func (in *Gateway) DeepCopy() *Gateway { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayConfig) DeepCopyInto(out *GatewayConfig) { +func (in *GatewayPlugin) DeepCopyInto(out *GatewayPlugin) { *out = *in - if in.Gateway != nil { - in, out := &in.Gateway, &out.Gateway - *out = new(types.NamespacedName) - **out = **in + if in.ExternalGateways != nil { + in, out := &in.ExternalGateways, &out.ExternalGateways + *out = make([]Gateway, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } - if in.Service != nil { - in, out := &in.Service, &out.Service - *out = new(types.NamespacedName) - **out = **in + if in.LocalGateways != nil { + in, out := &in.LocalGateways, &out.LocalGateways + *out = make([]Gateway, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayConfig. -func (in *GatewayConfig) DeepCopy() *GatewayConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPlugin. +func (in *GatewayPlugin) DeepCopy() *GatewayPlugin { if in == nil { return nil } - out := new(GatewayConfig) + out := new(GatewayPlugin) in.DeepCopyInto(out) return out } diff --git a/pkg/reconciler/ingress/controller.go b/pkg/reconciler/ingress/controller.go index 0de6613e5..070640c8f 100644 --- a/pkg/reconciler/ingress/controller.go +++ b/pkg/reconciler/ingress/controller.go @@ -73,7 +73,7 @@ func NewController( impl := ingressreconciler.NewImpl(ctx, c, gatewayAPIIngressClassName, func(impl *controller.Impl) controller.Options { configsToResync := []interface{}{ &networkcfg.Config{}, - &config.Gateway{}, + &config.GatewayPlugin{}, } resync := configmap.TypeFilter(configsToResync...)(func(string, interface{}) { impl.GlobalResync(ingressInformer.Informer()) diff --git a/pkg/reconciler/ingress/ingress.go b/pkg/reconciler/ingress/ingress.go index e64065b2c..c2099e3d2 100644 --- a/pkg/reconciler/ingress/ingress.go +++ b/pkg/reconciler/ingress/ingress.go @@ -74,14 +74,14 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, ingress *v1alpha1.Ingres // FinalizeKind implements Interface.FinalizeKind func (c *Reconciler) FinalizeKind(ctx context.Context, ingress *v1alpha1.Ingress) pkgreconciler.Event { - gatewayConfig := config.FromContext(ctx).Gateway + pluginConfig := config.FromContext(ctx).GatewayPlugin // We currently only support TLS on the external IP - return c.clearGatewayListeners(ctx, ingress, gatewayConfig.Gateways[v1alpha1.IngressVisibilityExternalIP].Gateway) + return c.clearGatewayListeners(ctx, ingress, pluginConfig.ExternalGateway().NamespacedName) } func (c *Reconciler) reconcileIngress(ctx context.Context, ing *v1alpha1.Ingress) error { - gatewayConfig := config.FromContext(ctx).Gateway + pluginConfig := config.FromContext(ctx).GatewayPlugin // We may be reading a version of the object that was stored at an older version // and may not have had all of the assumed defaults specified. This won't result @@ -140,7 +140,7 @@ func (c *Reconciler) reconcileIngress(ctx context.Context, ing *v1alpha1.Ingress // For now, we only reconcile the external visibility, because there's // no way to provide TLS for internal listeners. err := c.reconcileGatewayListeners( - ctx, listeners, ing, *gatewayConfig.Gateways[v1alpha1.IngressVisibilityExternalIP].Gateway) + ctx, listeners, ing, pluginConfig.ExternalGateway().NamespacedName) if err != nil { return err } @@ -150,38 +150,38 @@ func (c *Reconciler) reconcileIngress(ctx context.Context, ing *v1alpha1.Ingress if routesReady { var publicLbs, privateLbs []v1alpha1.LoadBalancerIngressStatus - externalIPGatewayConfig := gatewayConfig.Gateways[v1alpha1.IngressVisibilityExternalIP] - internalIPGatewayConfig := gatewayConfig.Gateways[v1alpha1.IngressVisibilityClusterLocal] + externalGateway := pluginConfig.ExternalGateway() + localGateway := pluginConfig.LocalGateway() - publicLbs, err = c.determineLoadBalancerIngressStatus(externalIPGatewayConfig) + publicLbs, err = c.determineLoadBalancerIngressStatus(externalGateway) if err != nil { if apierrs.IsNotFound(err) { ing.Status.MarkLoadBalancerFailed( "GatewayDoesNotExist", fmt.Sprintf( "could not find Gateway %s/%s", - externalIPGatewayConfig.Gateway.Namespace, - externalIPGatewayConfig.Gateway.Name, + externalGateway.Namespace, + externalGateway.Name, ), ) - return fmt.Errorf("Gateway %s does not exist: %w", externalIPGatewayConfig.Gateway.Name, err) //nolint:stylecheck + return fmt.Errorf("Gateway %s does not exist: %w", externalGateway.Name, err) //nolint:stylecheck } ing.Status.MarkLoadBalancerNotReady() return err } - privateLbs, err = c.determineLoadBalancerIngressStatus(internalIPGatewayConfig) + privateLbs, err = c.determineLoadBalancerIngressStatus(localGateway) if err != nil { if apierrs.IsNotFound(err) { ing.Status.MarkLoadBalancerFailed( "GatewayDoesNotExist", fmt.Sprintf( "could not find Gateway %s/%s", - internalIPGatewayConfig.Gateway.Namespace, - internalIPGatewayConfig.Gateway.Name, + localGateway.Namespace, + localGateway.Name, ), ) - return fmt.Errorf("Gateway %s does not exist: %w", internalIPGatewayConfig.Gateway.Name, err) //nolint:stylecheck + return fmt.Errorf("Gateway %s does not exist: %w", localGateway.Name, err) //nolint:stylecheck } ing.Status.MarkLoadBalancerNotReady() return err @@ -198,13 +198,13 @@ func (c *Reconciler) reconcileIngress(ctx context.Context, ing *v1alpha1.Ingress // determineLoadBalancerIngressStatus will return the address for the Gateway. // If a service is provided, it will return the address of the service. // Otherwise, it will return the first address in the Gateway status. -func (c *Reconciler) determineLoadBalancerIngressStatus(gwc config.GatewayConfig) ([]v1alpha1.LoadBalancerIngressStatus, error) { +func (c *Reconciler) determineLoadBalancerIngressStatus(gwc config.Gateway) ([]v1alpha1.LoadBalancerIngressStatus, error) { if gwc.Service != nil { return []v1alpha1.LoadBalancerIngressStatus{ {DomainInternal: network.GetServiceHostname(gwc.Service.Name, gwc.Service.Namespace)}, }, nil } - gw, err := c.gatewayLister.Gateways(gwc.Gateway.Namespace).Get(gwc.Gateway.Name) + gw, err := c.gatewayLister.Gateways(gwc.Namespace).Get(gwc.Name) if err != nil { return nil, err } @@ -223,7 +223,7 @@ func (c *Reconciler) determineLoadBalancerIngressStatus(gwc config.GatewayConfig return []v1alpha1.LoadBalancerIngressStatus{lbis}, nil } - return nil, fmt.Errorf("Gateway %s does not have an address in status", gwc.Gateway.Name) //nolint:stylecheck + return nil, fmt.Errorf("Gateway %q does not have an address in status", gwc.NamespacedName) //nolint:stylecheck } diff --git a/pkg/reconciler/ingress/ingress_test.go b/pkg/reconciler/ingress/ingress_test.go index 8184e490d..6c4c00550 100644 --- a/pkg/reconciler/ingress/ingress_test.go +++ b/pkg/reconciler/ingress/ingress_test.go @@ -2268,7 +2268,7 @@ func TestReconcileProbingOffClusterGateway(t *testing.T) { }, }, WantEvents: []string{ - Eventf(corev1.EventTypeWarning, "InternalError", `Gateway istio-gateway does not have an address in status`), + Eventf(corev1.EventTypeWarning, "InternalError", `Gateway "istio-system/istio-gateway" does not have an address in status`), }, }} @@ -2518,31 +2518,27 @@ func rp(to *corev1.Secret) *gatewayapiv1beta1.ReferenceGrant { var ( defaultConfig = &config.Config{ Network: &networkcfg.Config{}, - Gateway: &config.Gateway{ - Gateways: map[v1alpha1.IngressVisibility]config.GatewayConfig{ - v1alpha1.IngressVisibilityExternalIP: { - Service: &types.NamespacedName{Namespace: "istio-system", Name: "istio-gateway"}, - Gateway: &types.NamespacedName{Namespace: "istio-system", Name: "istio-gateway"}, - }, - v1alpha1.IngressVisibilityClusterLocal: { - Service: &types.NamespacedName{Namespace: "istio-system", Name: "knative-local-gateway"}, - Gateway: &types.NamespacedName{Namespace: "istio-system", Name: "knative-local-gateway"}, - }, - }, + GatewayPlugin: &config.GatewayPlugin{ + ExternalGateways: []config.Gateway{{ + Service: &types.NamespacedName{Namespace: "istio-system", Name: "istio-gateway"}, + NamespacedName: types.NamespacedName{Namespace: "istio-system", Name: "istio-gateway"}, + }}, + LocalGateways: []config.Gateway{{ + Service: &types.NamespacedName{Namespace: "istio-system", Name: "knative-local-gateway"}, + NamespacedName: types.NamespacedName{Namespace: "istio-system", Name: "knative-local-gateway"}, + }}, }, } configNoService = &config.Config{ Network: &networkcfg.Config{}, - Gateway: &config.Gateway{ - Gateways: map[v1alpha1.IngressVisibility]config.GatewayConfig{ - v1alpha1.IngressVisibilityExternalIP: { - Gateway: &types.NamespacedName{Namespace: "istio-system", Name: "istio-gateway"}, - }, - v1alpha1.IngressVisibilityClusterLocal: { - Gateway: &types.NamespacedName{Namespace: "istio-system", Name: "knative-local-gateway"}, - }, - }, + GatewayPlugin: &config.GatewayPlugin{ + ExternalGateways: []config.Gateway{{ + NamespacedName: types.NamespacedName{Namespace: "istio-system", Name: "istio-gateway"}, + }}, + LocalGateways: []config.Gateway{{ + NamespacedName: types.NamespacedName{Namespace: "istio-system", Name: "knative-local-gateway"}, + }}, }, } ) diff --git a/pkg/reconciler/ingress/lister.go b/pkg/reconciler/ingress/lister.go index 45adceabb..8e81bf889 100644 --- a/pkg/reconciler/ingress/lister.go +++ b/pkg/reconciler/ingress/lister.go @@ -47,13 +47,20 @@ type gatewayPodTargetLister struct { } func (l *gatewayPodTargetLister) BackendsToProbeTargets(ctx context.Context, backends status.Backends) ([]status.ProbeTarget, error) { - gatewayConfig := config.FromContext(ctx).Gateway + pluginConfig := config.FromContext(ctx).GatewayPlugin foundTargets := 0 targets := make([]status.ProbeTarget, 0, len(backends.URLs)) for visibility, urls := range backends.URLs { - if service := gatewayConfig.Gateways[visibility].Service; service != nil { + var gateway config.Gateway + if visibility == v1alpha1.IngressVisibilityClusterLocal { + gateway = pluginConfig.LocalGateway() + } else { + gateway = pluginConfig.ExternalGateway() + } + + if service := gateway.Service; service != nil { eps, err := l.endpointsLister.Endpoints(service.Namespace).Get(service.Name) if err != nil { return nil, fmt.Errorf("failed to get endpoints: %w", err) @@ -98,10 +105,9 @@ func (l *gatewayPodTargetLister) BackendsToProbeTargets(ctx context.Context, bac } } } else { - gwName := gatewayConfig.Gateways[visibility].Gateway - gw, err := l.gatewayLister.Gateways(gwName.Namespace).Get(gwName.Name) + gw, err := l.gatewayLister.Gateways(gateway.Namespace).Get(gateway.Name) if apierrs.IsNotFound(err) { - return nil, fmt.Errorf("Gateway %s does not exist: %w", gwName, err) //nolint:stylecheck + return nil, fmt.Errorf("Gateway %q does not exist: %w", gateway, err) //nolint:stylecheck } else if err != nil { return nil, err } diff --git a/pkg/reconciler/ingress/reconcile_resources.go b/pkg/reconciler/ingress/reconcile_resources.go index 34d090013..f0229b7e0 100644 --- a/pkg/reconciler/ingress/reconcile_resources.go +++ b/pkg/reconciler/ingress/reconcile_resources.go @@ -221,8 +221,7 @@ func (c *Reconciler) reconcileTLS( ) ( []*gatewayapi.Listener, error) { recorder := controller.GetEventRecorder(ctx) - gatewayConfig := config.FromContext(ctx).Gateway.Gateways - externalGw := gatewayConfig[netv1alpha1.IngressVisibilityExternalIP] + externalGw := config.FromContext(ctx).GatewayPlugin.ExternalGateway() gateway := metav1.PartialObjectMetadata{ TypeMeta: metav1.TypeMeta{ @@ -230,8 +229,8 @@ func (c *Reconciler) reconcileTLS( APIVersion: gatewayapi.GroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ - Name: externalGw.Gateway.Name, - Namespace: externalGw.Gateway.Namespace, + Name: externalGw.Name, + Namespace: externalGw.Namespace, }, } secret := metav1.PartialObjectMetadata{ @@ -372,7 +371,7 @@ func (c *Reconciler) reconcileGatewayListeners( return nil } -func (c *Reconciler) clearGatewayListeners(ctx context.Context, ing *netv1alpha1.Ingress, gwName *types.NamespacedName) error { +func (c *Reconciler) clearGatewayListeners(ctx context.Context, ing *netv1alpha1.Ingress, gwName types.NamespacedName) error { recorder := controller.GetEventRecorder(ctx) gw, err := c.gatewayLister.Gateways(gwName.Namespace).Get(gwName.Name) diff --git a/pkg/reconciler/ingress/resources/httproute.go b/pkg/reconciler/ingress/resources/httproute.go index 5039221f8..31d2ffc2f 100644 --- a/pkg/reconciler/ingress/resources/httproute.go +++ b/pkg/reconciler/ingress/resources/httproute.go @@ -227,14 +227,21 @@ func makeHTTPRouteSpec( rules := makeHTTPRouteRule(rule) - gatewayConfig := config.FromContext(ctx).Gateway - namespacedNameGateway := gatewayConfig.Gateways[rule.Visibility].Gateway + pluginConfig := config.FromContext(ctx).GatewayPlugin + + var gateway config.Gateway + + if rule.Visibility == netv1alpha1.IngressVisibilityClusterLocal { + gateway = pluginConfig.LocalGateway() + } else { + gateway = pluginConfig.ExternalGateway() + } gatewayRef := gatewayapi.ParentReference{ Group: (*gatewayapi.Group)(&gatewayapi.GroupVersion.Group), Kind: (*gatewayapi.Kind)(ptr.To("Gateway")), - Namespace: ptr.To(gatewayapi.Namespace(namespacedNameGateway.Namespace)), - Name: gatewayapi.ObjectName(namespacedNameGateway.Name), + Namespace: ptr.To(gatewayapi.Namespace(gateway.Namespace)), + Name: gatewayapi.ObjectName(gateway.Name), } return gatewayapi.HTTPRouteSpec{ diff --git a/pkg/reconciler/ingress/resources/httproute_test.go b/pkg/reconciler/ingress/resources/httproute_test.go index 78b3aa930..8441a3569 100644 --- a/pkg/reconciler/ingress/resources/httproute_test.go +++ b/pkg/reconciler/ingress/resources/httproute_test.go @@ -1123,17 +1123,16 @@ func (t *testConfigStore) ToContext(ctx context.Context) context.Context { } var testConfig = &config.Config{ - Gateway: &config.Gateway{ - Gateways: map[v1alpha1.IngressVisibility]config.GatewayConfig{ - v1alpha1.IngressVisibilityExternalIP: { - GatewayClass: testGatewayClass, - Gateway: &types.NamespacedName{Namespace: "test-ns", Name: "foo"}, - }, - v1alpha1.IngressVisibilityClusterLocal: { - GatewayClass: testGatewayClass, - Gateway: &types.NamespacedName{Namespace: "test-ns", Name: "foo-local"}, - }, + GatewayPlugin: &config.GatewayPlugin{ + ExternalGateways: []config.Gateway{{ + NamespacedName: types.NamespacedName{Namespace: "test-ns", Name: "foo"}, + Class: testGatewayClass, + }}, + LocalGateways: []config.Gateway{{ + NamespacedName: types.NamespacedName{Namespace: "test-ns", Name: "foo-local"}, + Class: testGatewayClass, }}, + }, } var _ reconciler.ConfigStore = (*testConfigStore)(nil) diff --git a/test/e2e/gateway_config_test.go b/test/e2e/gateway_config_test.go index 30ebe5fbf..e215809ee 100644 --- a/test/e2e/gateway_config_test.go +++ b/test/e2e/gateway_config_test.go @@ -69,9 +69,9 @@ func TestNetGatewayAPIConfigNoService(t *testing.T) { if ingress, ok := os.LookupEnv("INGRESS"); ok { switch ingress { case "contour": - configGateway = ConfigMapFromTestFile(t, testdata+"contour-no-service-vis.yaml", "visibility") + configGateway = ConfigMapFromTestFile(t, testdata+"contour-no-service-vis.yaml", "external-gateways", "local-gateways") case "istio": - configGateway = ConfigMapFromTestFile(t, testdata+"istio-no-service-vis.yaml", "visibility") + configGateway = ConfigMapFromTestFile(t, testdata+"istio-no-service-vis.yaml", "external-gateways", "local-gateways") case "default": t.Fatalf("value for INGRESS (%s) not supported", ingress) } diff --git a/test/e2e/testdata/contour-no-service-vis.yaml b/test/e2e/testdata/contour-no-service-vis.yaml index 3bb77cc36..8a6ce8551 100644 --- a/test/e2e/testdata/contour-no-service-vis.yaml +++ b/test/e2e/testdata/contour-no-service-vis.yaml @@ -24,11 +24,10 @@ metadata: labels: serving.knative.dev/release: devel data: - visibility: | - ExternalIP: - class: contour + external-gateways: | + - class: contour gateway: contour-external/knative-external - ClusterLocal: - class: contour + local-gateways: | + - class: contour gateway: contour-internal/knative-local service: contour-internal/envoy-knative-local diff --git a/test/e2e/testdata/istio-no-service-vis.yaml b/test/e2e/testdata/istio-no-service-vis.yaml index d840086a6..c934762ca 100644 --- a/test/e2e/testdata/istio-no-service-vis.yaml +++ b/test/e2e/testdata/istio-no-service-vis.yaml @@ -20,10 +20,9 @@ metadata: labels: serving.knative.dev/release: devel data: - visibility: | - ExternalIP: - class: istio + external-gateways: | + - class: istio gateway: istio-system/knative-gateway - ClusterLocal: - class: istio + local-gateways: | + - class: istio gateway: istio-system/knative-local-gateway diff --git a/third_party/contour/config-gateway.yaml b/third_party/contour/config-gateway.yaml index 69408dfba..7aa9db95e 100644 --- a/third_party/contour/config-gateway.yaml +++ b/third_party/contour/config-gateway.yaml @@ -20,12 +20,13 @@ metadata: labels: serving.knative.dev/release: devel data: - visibility: | - ExternalIP: - class: contour + external-gateways: | + - class: contour gateway: contour-external/knative-external service: contour-external/envoy-knative-external - ClusterLocal: - class: contour + + # local-gateways defines the Gateway to be used for cluster local traffic + local-gateways: | + - class: contour gateway: contour-internal/knative-local service: contour-internal/envoy-knative-local