From cc248dcdc455f66699b73bd5c347d30ce1ccd4b6 Mon Sep 17 00:00:00 2001 From: kahirokunn Date: Mon, 13 Feb 2023 14:55:11 +0900 Subject: [PATCH 1/2] Add `--envoy-ingress-name`, and `--envoy-ingress-namespace` flags. (#4952) Signed-off-by: kahirokunn --- apis/projectcontour/v1alpha1/contourconfig.go | 6 ++ cmd/contour/serve.go | 46 ++++++++++++++ cmd/contour/servecontext.go | 4 ++ internal/k8s/statusaddress.go | 60 +++++++++++++++++++ internal/provisioner/model/names.go | 5 ++ .../objects/contourconfig/contourconfig.go | 4 ++ .../objects/deployment/deployment.go | 1 + pkg/config/parameters.go | 8 +++ 8 files changed, 134 insertions(+) diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index 16845ac6b55..dc9ae2b0bf0 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -239,6 +239,12 @@ type EnvoyConfig struct { // +optional Service *NamespacedName `json:"service,omitempty"` + // Ingress holds Envoy service parameters for setting Ingress status. + // + // Contour's default is { namespace: "projectcontour", name: "envoy" }. + // +optional + Ingress *NamespacedName `json:"ingress,omitempty"` + // Defines the HTTP Listener for Envoy. // // Contour's default is { address: "0.0.0.0", port: 8080, accessLog: "/dev/stdout" }. diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 16a00bdd283..3f22cbdcf13 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -146,6 +146,8 @@ func registerServe(app *kingpin.Application) (*kingpin.CmdClause, *serveContext) serve.Flag("envoy-service-https-port", "Kubernetes Service port for HTTPS requests.").PlaceHolder("").IntVar(&ctx.httpsPort) serve.Flag("envoy-service-name", "Name of the Envoy service to inspect for Ingress status details.").PlaceHolder("").StringVar(&ctx.Config.EnvoyServiceName) serve.Flag("envoy-service-namespace", "Envoy Service Namespace.").PlaceHolder("").StringVar(&ctx.Config.EnvoyServiceNamespace) + serve.Flag("envoy-ingress-name", "Name of the Envoy ingress to inspect for Ingress status details.").PlaceHolder("").StringVar(&ctx.Config.EnvoyIngressName) + serve.Flag("envoy-ingress-namespace", "Envoy Ingress Namespace.").PlaceHolder("").StringVar(&ctx.Config.EnvoyIngressNamespace) serve.Flag("health-address", "Address the health HTTP endpoint will bind to.").PlaceHolder("").StringVar(&ctx.healthAddr) serve.Flag("health-port", "Port the health HTTP endpoint will bind to.").PlaceHolder("").IntVar(&ctx.healthPort) @@ -688,6 +690,50 @@ func (s *Server) doServe() error { return err } + // Register an informer to watch envoy's service if we haven't been given static details. + if lbAddress := contourConfiguration.Ingress.StatusAddress; len(lbAddress) > 0 { + s.log.WithField("loadbalancer-address", lbAddress).Info("Using supplied information for Ingress status") + lbsw.lbStatus <- parseStatusFlag(lbAddress) + } else { + serviceHandler := &k8s.ServiceStatusLoadBalancerWatcher{ + ServiceName: contourConfiguration.Envoy.Service.Name, + LBStatus: lbsw.lbStatus, + Log: s.log.WithField("context", "serviceStatusLoadBalancerWatcher"), + } + + var handler cache.ResourceEventHandler = serviceHandler + if contourConfiguration.Envoy.Service.Namespace != "" { + handler = k8s.NewNamespaceFilter([]string{contourConfiguration.Envoy.Service.Namespace}, handler) + } + + if err := s.informOnResource(&corev1.Service{}, handler); err != nil { + s.log.WithError(err).WithField("resource", "services").Fatal("failed to create informer") + } + + ingressHandler := &k8s.IngressStatusLoadBalancerWatcher{ + ServiceName: contourConfiguration.Envoy.Service.Name, + LBStatus: lbsw.lbStatus, + Log: s.log.WithField("context", "ingressStatusLoadBalancerWatcher"), + } + + var ingressEventHandler cache.ResourceEventHandler = ingressHandler + if contourConfiguration.Envoy.Ingress.Namespace != "" { + handler = k8s.NewNamespaceFilter([]string{contourConfiguration.Envoy.Ingress.Namespace}, handler) + } + + if err := informOnResource(&networking_v1.Ingress{}, ingressEventHandler, s.mgr.GetCache()); err != nil { + s.log.WithError(err).WithField("resource", "ingresses").Fatal("failed to create ingresses informer") + } + + s.log.WithField("envoy-service-name", contourConfiguration.Envoy.Service.Name). + WithField("envoy-service-namespace", contourConfiguration.Envoy.Service.Namespace). + Info("Watching Service for Ingress status") + + s.log.WithField("envoy-ingress-name", contourConfiguration.Envoy.Ingress.Name). + WithField("envoy-ingress-namespace", contourConfiguration.Envoy.Ingress.Namespace). + Info("Watching Ingress for Ingress status") + } + xdsServer := &xdsServer{ log: s.log, registry: s.registry, diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index 8c4ee27d73b..c2c770902a8 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -571,6 +571,10 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co Name: ctx.Config.EnvoyServiceName, Namespace: ctx.Config.EnvoyServiceNamespace, }, + Ingress: &contour_v1alpha1.NamespacedName{ + Name: ctx.Config.EnvoyIngressName, + Namespace: ctx.Config.EnvoyIngressNamespace, + }, HTTPListener: &contour_v1alpha1.EnvoyListener{ Address: ctx.httpAddr, Port: ctx.httpPort, diff --git a/internal/k8s/statusaddress.go b/internal/k8s/statusaddress.go index 0cb5cc1daa2..b7263c3af2f 100644 --- a/internal/k8s/statusaddress.go +++ b/internal/k8s/statusaddress.go @@ -280,3 +280,63 @@ func lbStatusToGatewayAddresses(lbs core_v1.LoadBalancerStatus) []gatewayapi_v1. return addrs } + +// IngressStatusLoadBalancerWatcher implements ResourceEventHandler and +// watches for changes to the status.loadbalancer field +// Note that we specifically *don't* inspect inside the struct, as sending empty values +// is desirable to clear the status. +type IngressStatusLoadBalancerWatcher struct { + IngressName string + LBStatus chan networking_v1.IngressLoadBalancerStatus + Log logrus.FieldLogger +} + +func (s *IngressStatusLoadBalancerWatcher) OnAdd(obj interface{}) { + ingress, ok := obj.(*networking_v1.Ingress) + if !ok { + // not a service + return + } + if ingress.Name != s.IngressName { + return + } + s.Log.WithField("name", ingress.Name). + WithField("namespace", ingress.Namespace). + Debug("received new service address") + + s.notify(ingress.Status.LoadBalancer) +} + +func (s *IngressStatusLoadBalancerWatcher) OnUpdate(oldObj, newObj interface{}) { + ingress, ok := newObj.(*networking_v1.Ingress) + if !ok { + // not a service + return + } + if ingress.Name != s.IngressName { + return + } + s.Log.WithField("name", ingress.Name). + WithField("namespace", ingress.Namespace). + Debug("received new service address") + + s.notify(ingress.Status.LoadBalancer) +} + +func (s *IngressStatusLoadBalancerWatcher) OnDelete(obj interface{}) { + ingress, ok := obj.(*networking_v1.Ingress) + if !ok { + // not a service + return + } + if ingress.Name != s.IngressName { + return + } + s.notify(networking_v1.IngressLoadBalancerStatus{ + Ingress: nil, + }) +} + +func (s *IngressStatusLoadBalancerWatcher) notify(lbstatus networking_v1.IngressLoadBalancerStatus) { + s.LBStatus <- lbstatus +} diff --git a/internal/provisioner/model/names.go b/internal/provisioner/model/names.go index 3f82ca1a77f..e2a3968f1a9 100644 --- a/internal/provisioner/model/names.go +++ b/internal/provisioner/model/names.go @@ -32,6 +32,11 @@ func (c *Contour) EnvoyServiceName() string { return "envoy-" + c.Name } +// EnvoyIngressName returns the name of the Envoy Ingress resource. +func (c *Contour) EnvoyIngressName() string { + return "envoy-" + c.Name +} + // ContourDeploymentName returns the name of the Contour Deployment resource. func (c *Contour) ContourDeploymentName() string { return "contour-" + c.Name diff --git a/internal/provisioner/objects/contourconfig/contourconfig.go b/internal/provisioner/objects/contourconfig/contourconfig.go index 1591125851e..497cdac5247 100644 --- a/internal/provisioner/objects/contourconfig/contourconfig.go +++ b/internal/provisioner/objects/contourconfig/contourconfig.go @@ -73,6 +73,10 @@ func setGatewayConfig(config *contour_v1alpha1.ContourConfiguration, contour *mo Namespace: contour.Namespace, Name: contour.EnvoyServiceName(), } + config.Spec.Envoy.Ingress = &contour_api_v1alpha1.NamespacedName{ + Namespace: contour.Namespace, + Name: contour.EnvoyIngressName(), + } } // EnsureContourConfigDeleted deletes a ContourConfig for the provided contour, if the configured owner labels exist. diff --git a/internal/provisioner/objects/deployment/deployment.go b/internal/provisioner/objects/deployment/deployment.go index 0c0b56a1113..d2ffc7e2dda 100644 --- a/internal/provisioner/objects/deployment/deployment.go +++ b/internal/provisioner/objects/deployment/deployment.go @@ -95,6 +95,7 @@ func DesiredDeployment(contour *model.Contour, image string) *apps_v1.Deployment fmt.Sprintf("--contour-config-name=%s", contour.ContourConfigurationName()), fmt.Sprintf("--leader-election-resource-name=%s", contour.LeaderElectionLeaseName()), fmt.Sprintf("--envoy-service-name=%s", contour.EnvoyServiceName()), + fmt.Sprintf("--envoy-ingress-name=%s", contour.EnvoyIngressName()), fmt.Sprintf("--kubernetes-debug=%d", contour.Spec.KubernetesLogLevel), } diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index 1e4c397d513..7377b6aba4c 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -667,6 +667,12 @@ type Parameters struct { // Name of the envoy service to inspect for Ingress status details. EnvoyServiceName string `yaml:"envoy-service-name,omitempty"` + // Namespace of the envoy ingress to inspect for Ingress status details. + EnvoyIngressNamespace string `yaml:"envoy-ingress-namespace,omitempty"` + + // Name of the envoy ingress to inspect for Ingress status details. + EnvoyIngressName string `yaml:"envoy-ingress-name,omitempty"` + // DefaultHTTPVersions defines the default set of HTTPS // versions the proxy should accept. HTTP versions are // strings of the form "HTTP/xx". Supported versions are @@ -1093,6 +1099,8 @@ func Defaults() Parameters { }, EnvoyServiceName: "envoy", EnvoyServiceNamespace: contourNamespace, + EnvoyIngressName: "envoy", + EnvoyIngressNamespace: contourNamespace, DefaultHTTPVersions: []HTTPVersionType{}, Cluster: ClusterParameters{ DNSLookupFamily: AutoClusterDNSFamily, From c238f416e0bd3e94a055974aa19a17ede39d4302 Mon Sep 17 00:00:00 2001 From: Haitao Li Date: Fri, 24 Nov 2023 22:57:22 +1100 Subject: [PATCH 2/2] Add a new load-balancer-status flag for setting ingress details Signed-off-by: Haitao Li --- apis/projectcontour/v1alpha1/contourconfig.go | 10 +- cmd/contour/serve.go | 154 +++++++++++++----- cmd/contour/serve_test.go | 106 ++++++++++++ cmd/contour/servecontext.go | 5 +- examples/contour/01-crds.yaml | 18 ++ examples/render/contour-deployment.yaml | 18 ++ .../render/contour-gateway-provisioner.yaml | 18 ++ examples/render/contour-gateway.yaml | 18 ++ examples/render/contour.yaml | 18 ++ internal/k8s/statusaddress.go | 32 +++- .../objects/contourconfig/contourconfig.go | 4 - pkg/config/parameters.go | 10 +- .../docs/main/config/api-reference.html | 18 ++ site/content/docs/main/configuration.md | 93 +++++------ 14 files changed, 414 insertions(+), 108 deletions(-) diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index dc9ae2b0bf0..9a4730e6eef 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -239,11 +239,15 @@ type EnvoyConfig struct { // +optional Service *NamespacedName `json:"service,omitempty"` - // Ingress holds Envoy service parameters for setting Ingress status. + // LoadBalancer specifies how Contour should set the ingress status address. + // If provided, the value can be in one of the formats: + // - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + // - service:/: Contour will use the address of the designated service. + // - ingress:/: Contour will use the address of the designated ingress. // - // Contour's default is { namespace: "projectcontour", name: "envoy" }. + // Contour's default is an empty string. // +optional - Ingress *NamespacedName `json:"ingress,omitempty"` + LoadBalancer string `json:"loadBalancer,omitempty"` // Defines the HTTP Listener for Envoy. // diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 3f22cbdcf13..c30056f8740 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -20,6 +20,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" "github.com/alecthomas/kingpin/v2" @@ -146,8 +147,6 @@ func registerServe(app *kingpin.Application) (*kingpin.CmdClause, *serveContext) serve.Flag("envoy-service-https-port", "Kubernetes Service port for HTTPS requests.").PlaceHolder("").IntVar(&ctx.httpsPort) serve.Flag("envoy-service-name", "Name of the Envoy service to inspect for Ingress status details.").PlaceHolder("").StringVar(&ctx.Config.EnvoyServiceName) serve.Flag("envoy-service-namespace", "Envoy Service Namespace.").PlaceHolder("").StringVar(&ctx.Config.EnvoyServiceNamespace) - serve.Flag("envoy-ingress-name", "Name of the Envoy ingress to inspect for Ingress status details.").PlaceHolder("").StringVar(&ctx.Config.EnvoyIngressName) - serve.Flag("envoy-ingress-namespace", "Envoy Ingress Namespace.").PlaceHolder("").StringVar(&ctx.Config.EnvoyIngressNamespace) serve.Flag("health-address", "Address the health HTTP endpoint will bind to.").PlaceHolder("").StringVar(&ctx.healthAddr) serve.Flag("health-port", "Port the health HTTP endpoint will bind to.").PlaceHolder("").IntVar(&ctx.healthPort) @@ -170,6 +169,8 @@ func registerServe(app *kingpin.Application) (*kingpin.CmdClause, *serveContext) serve.Flag("leader-election-resource-namespace", "The namespace of the resource (Lease) leader election will lease.").Default(config.GetenvOr("CONTOUR_NAMESPACE", "projectcontour")).StringVar(&ctx.LeaderElection.Namespace) serve.Flag("leader-election-retry-period", "The interval which Contour will attempt to acquire leadership lease.").Default("2s").DurationVar(&ctx.LeaderElection.RetryPeriod) + serve.Flag("load-balancer-status", "Address to set or the source to inspect for ingress status.").PlaceHolder("").StringVar(&ctx.Config.LoadBalancerStatus) + serve.Flag("root-namespaces", "Restrict contour to searching these namespaces for root ingress routes.").PlaceHolder("").StringVar(&ctx.rootNamespaces) serve.Flag("stats-address", "Envoy /stats interface address.").PlaceHolder("").StringVar(&ctx.statsAddr) @@ -675,13 +676,46 @@ func (s *Server) doServe() error { } // Set up ingress load balancer status writer. + if err := s.setupIngressLoadBalancerStatusWriter(contourConfiguration, ingressClassNames, gatewayRef, sh.Writer()); err != nil { + return err + } + + xdsServer := &xdsServer{ + log: s.log, + registry: s.registry, + config: *contourConfiguration.XDSServer, + snapshotHandler: snapshotHandler, + resources: resources, + initialDagBuilt: contourHandler.HasBuiltInitialDag, + } + if err := s.mgr.Add(xdsServer); err != nil { + return err + } + + notifier := &leadership.Notifier{ + ToNotify: []leadership.NeedLeaderElectionNotification{contourHandler, observer}, + } + if err := s.mgr.Add(notifier); err != nil { + return err + } + + // GO! + return s.mgr.Start(signals.SetupSignalHandler()) +} + +func (s *Server) setupIngressLoadBalancerStatusWriter( + contourConfiguration contour_v1alpha1.ContourConfigurationSpec, + ingressClassNames []string, + gatewayRef *types.NamespacedName, + statusUpdater k8s.StatusUpdater, +) error { lbsw := &loadBalancerStatusWriter{ log: s.log.WithField("context", "loadBalancerStatusWriter"), cache: s.mgr.GetCache(), lbStatus: make(chan core_v1.LoadBalancerStatus, 1), ingressClassNames: ingressClassNames, gatewayRef: gatewayRef, - statusUpdater: sh.Writer(), + statusUpdater: statusUpdater, statusAddress: contourConfiguration.Ingress.StatusAddress, serviceName: contourConfiguration.Envoy.Service.Name, serviceNamespace: contourConfiguration.Envoy.Service.Namespace, @@ -690,71 +724,113 @@ func (s *Server) doServe() error { return err } - // Register an informer to watch envoy's service if we haven't been given static details. + elbs := &envoyLoadBalancerStatus{} if lbAddress := contourConfiguration.Ingress.StatusAddress; len(lbAddress) > 0 { - s.log.WithField("loadbalancer-address", lbAddress).Info("Using supplied information for Ingress status") - lbsw.lbStatus <- parseStatusFlag(lbAddress) + elbs.Kind = "hostname" + elbs.FQDNs = lbAddress + } else if contourConfiguration.Envoy.LoadBalancer != "" { + status, err := parseEnvoyLoadBalancerStatus(contourConfiguration.Envoy.LoadBalancer) + if err != nil { + return err + } + elbs = status } else { + elbs.Kind = "service" + elbs.Namespace = contourConfiguration.Envoy.Service.Namespace + elbs.Name = contourConfiguration.Envoy.Service.Name + } + switch strings.ToLower(elbs.Kind) { + case "hostname": + s.log.WithField("loadbalancer-fqdns", lbAddress).Info("Using supplied hostname for Ingress status") + lbsw.lbStatus <- parseStatusFlag(elbs.FQDNs) + case "service": + // Register an informer to watch supplied service serviceHandler := &k8s.ServiceStatusLoadBalancerWatcher{ - ServiceName: contourConfiguration.Envoy.Service.Name, + ServiceName: elbs.Name, LBStatus: lbsw.lbStatus, Log: s.log.WithField("context", "serviceStatusLoadBalancerWatcher"), } var handler cache.ResourceEventHandler = serviceHandler - if contourConfiguration.Envoy.Service.Namespace != "" { - handler = k8s.NewNamespaceFilter([]string{contourConfiguration.Envoy.Service.Namespace}, handler) + if elbs.Namespace != "" { + handler = k8s.NewNamespaceFilter([]string{elbs.Namespace}, handler) } - if err := s.informOnResource(&corev1.Service{}, handler); err != nil { - s.log.WithError(err).WithField("resource", "services").Fatal("failed to create informer") + if err := s.informOnResource(&core_v1.Service{}, handler); err != nil { + s.log.WithError(err).WithField("resource", "services").Fatal("failed to create services informer") } - + s.log.Infof("Watching %s for Ingress status", elbs) + case "ingress": + // Register an informer to watch supplied ingress ingressHandler := &k8s.IngressStatusLoadBalancerWatcher{ - ServiceName: contourConfiguration.Envoy.Service.Name, + IngressName: elbs.Name, LBStatus: lbsw.lbStatus, Log: s.log.WithField("context", "ingressStatusLoadBalancerWatcher"), } - var ingressEventHandler cache.ResourceEventHandler = ingressHandler - if contourConfiguration.Envoy.Ingress.Namespace != "" { - handler = k8s.NewNamespaceFilter([]string{contourConfiguration.Envoy.Ingress.Namespace}, handler) + var handler cache.ResourceEventHandler = ingressHandler + if elbs.Namespace != "" { + handler = k8s.NewNamespaceFilter([]string{elbs.Namespace}, handler) } - if err := informOnResource(&networking_v1.Ingress{}, ingressEventHandler, s.mgr.GetCache()); err != nil { + if err := s.informOnResource(&networking_v1.Ingress{}, handler); err != nil { s.log.WithError(err).WithField("resource", "ingresses").Fatal("failed to create ingresses informer") } + s.log.Infof("Watching %s for Ingress status", elbs) + default: + return fmt.Errorf("unsupported ingress kind: %s", elbs.Kind) + } - s.log.WithField("envoy-service-name", contourConfiguration.Envoy.Service.Name). - WithField("envoy-service-namespace", contourConfiguration.Envoy.Service.Namespace). - Info("Watching Service for Ingress status") + return nil +} - s.log.WithField("envoy-ingress-name", contourConfiguration.Envoy.Ingress.Name). - WithField("envoy-ingress-namespace", contourConfiguration.Envoy.Ingress.Namespace). - Info("Watching Ingress for Ingress status") - } +type envoyLoadBalancerStatus struct { + Kind string + FQDNs string + config.NamespacedName +} - xdsServer := &xdsServer{ - log: s.log, - registry: s.registry, - config: *contourConfiguration.XDSServer, - snapshotHandler: snapshotHandler, - resources: resources, - initialDagBuilt: contourHandler.HasBuiltInitialDag, +func (elbs *envoyLoadBalancerStatus) String() string { + if elbs.Kind == "hostname" { + return fmt.Sprintf("%s:%s", elbs.Kind, elbs.FQDNs) } - if err := s.mgr.Add(xdsServer); err != nil { - return err + return fmt.Sprintf("%s:%s/%s", elbs.Kind, elbs.Namespace, elbs.Name) +} + +func parseEnvoyLoadBalancerStatus(s string) (*envoyLoadBalancerStatus, error) { + parts := strings.SplitN(s, ":", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid load-balancer-status: %s", s) } - notifier := &leadership.Notifier{ - ToNotify: []leadership.NeedLeaderElectionNotification{contourHandler, observer}, + if parts[1] == "" { + return nil, fmt.Errorf("invalid load-balancer-status: empty object reference") } - if err := s.mgr.Add(notifier); err != nil { - return err + + elbs := envoyLoadBalancerStatus{} + + elbs.Kind = strings.ToLower(parts[0]) + switch elbs.Kind { + case "ingress", "service": + parts = strings.Split(parts[1], "/") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid load-balancer-status: %s is not in the format of /", s) + } + + if parts[0] == "" || parts[1] == "" { + return nil, fmt.Errorf("invalid load-balancer-status: or is empty") + } + elbs.Namespace = parts[0] + elbs.Name = parts[1] + case "hostname": + elbs.FQDNs = parts[1] + case "": + return nil, fmt.Errorf("invalid load-balancer-status: kind is empty") + default: + return nil, fmt.Errorf("invalid load-balancer-status: unsupported kind: %s", elbs.Kind) } - // GO! - return s.mgr.Start(signals.SetupSignalHandler()) + return &elbs, nil } func (s *Server) getExtensionSvcConfig(name, namespace string) (xdscache_v3.ExtensionServiceConfig, error) { diff --git a/cmd/contour/serve_test.go b/cmd/contour/serve_test.go index 1cf5439a337..16af85a4b0f 100644 --- a/cmd/contour/serve_test.go +++ b/cmd/contour/serve_test.go @@ -24,6 +24,7 @@ import ( contour_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/dag" + "github.com/projectcontour/contour/pkg/config" ) func TestGetDAGBuilder(t *testing.T) { @@ -256,3 +257,108 @@ func mustGetIngressProcessor(t *testing.T, builder *dag.Builder) *dag.IngressPro require.FailNow(t, "IngressProcessor not found in list of DAG builder's processors") return nil } + +func TestParseEnvoyLoadBalancerStatus(t *testing.T) { + tests := []struct { + name string + status string + want envoyLoadBalancerStatus + }{ + { + name: "Service", + status: "service:namespace-1/name-1", + want: envoyLoadBalancerStatus{ + Kind: "service", + NamespacedName: config.NamespacedName{ + Name: "name-1", + Namespace: "namespace-1", + }, + }, + }, + { + name: "Ingress", + status: "ingress:namespace-1/name-1", + want: envoyLoadBalancerStatus{ + Kind: "ingress", + NamespacedName: config.NamespacedName{ + Name: "name-1", + Namespace: "namespace-1", + }, + }, + }, + { + name: "hostname", + status: "hostname:example.com", + want: envoyLoadBalancerStatus{ + Kind: "hostname", + FQDNs: "example.com", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := parseEnvoyLoadBalancerStatus(tt.status) + require.NoError(t, err) + assert.Equal(t, tt.want, *r) + }) + } + + tests2 := []struct { + name string + status string + error string + }{ + { + name: "Empty", + status: "", + error: "invalid", + }, + { + name: "No kind", + status: ":n", + error: "kind is empty", + }, + { + name: "Invalid kind", + status: "test:n", + error: "unsupported kind", + }, + { + name: "No reference", + status: "service:", + error: "empty object reference", + }, + { + name: "No colon", + status: "service", + error: "invalid", + }, + { + name: "No slash", + status: "service:name-1", + error: "not in the format", + }, + { + name: "starts with slash", + status: "service:/name-1", + error: "is empty", + }, + { + name: "ends with slash", + status: "service:name-1/", + error: "is empty", + }, + { + name: "two many slashes", + status: "service:name/x/y", + error: "not in the format", + }, + } + for _, tt := range tests2 { + t.Run(tt.name, func(t *testing.T) { + _, err := parseEnvoyLoadBalancerStatus(tt.status) + require.Error(t, err) + assert.Contains(t, err.Error(), tt.error) + }) + } +} diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index c2c770902a8..3ba0d688438 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -571,10 +571,6 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co Name: ctx.Config.EnvoyServiceName, Namespace: ctx.Config.EnvoyServiceNamespace, }, - Ingress: &contour_v1alpha1.NamespacedName{ - Name: ctx.Config.EnvoyIngressName, - Namespace: ctx.Config.EnvoyIngressNamespace, - }, HTTPListener: &contour_v1alpha1.EnvoyListener{ Address: ctx.httpAddr, Port: ctx.httpPort, @@ -616,6 +612,7 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co EnvoyStripTrailingHostDot: &ctx.Config.Network.EnvoyStripTrailingHostDot, }, OMEnforcedHealth: envoyOMEnforcedHealthListenerConfig, + LoadBalancer: ctx.Config.LoadBalancerStatus, }, Gateway: gatewayConfig, HTTPProxy: &contour_v1alpha1.HTTPProxyConfig{ diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index c84529ae934..9c986b0ad99 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -460,6 +460,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: @@ -4323,6 +4332,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index 051d4407997..ded4749c776 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -679,6 +679,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: @@ -4542,6 +4551,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index b81e837487f..8a937d71e05 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -471,6 +471,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: @@ -4334,6 +4343,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index a8929c25144..c76a682f6e3 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -496,6 +496,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: @@ -4359,6 +4368,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index eb70cef9cfa..325d146bd9f 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -679,6 +679,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: @@ -4542,6 +4551,15 @@ spec: Contour's default is false. type: boolean type: object + loadBalancer: + description: |- + LoadBalancer specifies how Contour should set the ingress status address. + If provided, the value can be in one of the formats: + - address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. + - service:/: Contour will use the address of the designated service. + - ingress:/: Contour will use the address of the designated ingress. + Contour's default is an empty string. + type: string logging: description: Logging defines how Envoy's logs can be configured. properties: diff --git a/internal/k8s/statusaddress.go b/internal/k8s/statusaddress.go index b7263c3af2f..fed0f6eaf9b 100644 --- a/internal/k8s/statusaddress.go +++ b/internal/k8s/statusaddress.go @@ -281,17 +281,39 @@ func lbStatusToGatewayAddresses(lbs core_v1.LoadBalancerStatus) []gatewayapi_v1. return addrs } +func networkingToCoreLBStatus(lbs networking_v1.IngressLoadBalancerStatus) core_v1.LoadBalancerStatus { + ingress := make([]core_v1.LoadBalancerIngress, len(lbs.Ingress)) + for i, ing := range lbs.Ingress { + ports := make([]core_v1.PortStatus, len(ing.Ports)) + for j, ps := range ing.Ports { + ports[j] = core_v1.PortStatus{ + Port: ps.Port, + Protocol: ps.Protocol, + Error: ps.Error, + } + } + ingress[i] = core_v1.LoadBalancerIngress{ + IP: ing.IP, + Hostname: ing.Hostname, + Ports: ports, + } + } + return core_v1.LoadBalancerStatus{ + Ingress: ingress, + } +} + // IngressStatusLoadBalancerWatcher implements ResourceEventHandler and // watches for changes to the status.loadbalancer field // Note that we specifically *don't* inspect inside the struct, as sending empty values // is desirable to clear the status. type IngressStatusLoadBalancerWatcher struct { IngressName string - LBStatus chan networking_v1.IngressLoadBalancerStatus + LBStatus chan core_v1.LoadBalancerStatus Log logrus.FieldLogger } -func (s *IngressStatusLoadBalancerWatcher) OnAdd(obj interface{}) { +func (s *IngressStatusLoadBalancerWatcher) OnAdd(obj any, _ bool) { ingress, ok := obj.(*networking_v1.Ingress) if !ok { // not a service @@ -307,7 +329,7 @@ func (s *IngressStatusLoadBalancerWatcher) OnAdd(obj interface{}) { s.notify(ingress.Status.LoadBalancer) } -func (s *IngressStatusLoadBalancerWatcher) OnUpdate(oldObj, newObj interface{}) { +func (s *IngressStatusLoadBalancerWatcher) OnUpdate(_, newObj any) { ingress, ok := newObj.(*networking_v1.Ingress) if !ok { // not a service @@ -323,7 +345,7 @@ func (s *IngressStatusLoadBalancerWatcher) OnUpdate(oldObj, newObj interface{}) s.notify(ingress.Status.LoadBalancer) } -func (s *IngressStatusLoadBalancerWatcher) OnDelete(obj interface{}) { +func (s *IngressStatusLoadBalancerWatcher) OnDelete(obj any) { ingress, ok := obj.(*networking_v1.Ingress) if !ok { // not a service @@ -338,5 +360,5 @@ func (s *IngressStatusLoadBalancerWatcher) OnDelete(obj interface{}) { } func (s *IngressStatusLoadBalancerWatcher) notify(lbstatus networking_v1.IngressLoadBalancerStatus) { - s.LBStatus <- lbstatus + s.LBStatus <- networkingToCoreLBStatus(lbstatus) } diff --git a/internal/provisioner/objects/contourconfig/contourconfig.go b/internal/provisioner/objects/contourconfig/contourconfig.go index 497cdac5247..1591125851e 100644 --- a/internal/provisioner/objects/contourconfig/contourconfig.go +++ b/internal/provisioner/objects/contourconfig/contourconfig.go @@ -73,10 +73,6 @@ func setGatewayConfig(config *contour_v1alpha1.ContourConfiguration, contour *mo Namespace: contour.Namespace, Name: contour.EnvoyServiceName(), } - config.Spec.Envoy.Ingress = &contour_api_v1alpha1.NamespacedName{ - Namespace: contour.Namespace, - Name: contour.EnvoyIngressName(), - } } // EnsureContourConfigDeleted deletes a ContourConfig for the provided contour, if the configured owner labels exist. diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index 7377b6aba4c..86b75c708f8 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -667,11 +667,9 @@ type Parameters struct { // Name of the envoy service to inspect for Ingress status details. EnvoyServiceName string `yaml:"envoy-service-name,omitempty"` - // Namespace of the envoy ingress to inspect for Ingress status details. - EnvoyIngressNamespace string `yaml:"envoy-ingress-namespace,omitempty"` - - // Name of the envoy ingress to inspect for Ingress status details. - EnvoyIngressName string `yaml:"envoy-ingress-name,omitempty"` + // Identifier of ingress object for load balancer address in the format of + // (ingress|service):/, or hostname:fqdn1[,fqdn2]. + LoadBalancerStatus string `yaml:"load-balancer-status,omitempty"` // DefaultHTTPVersions defines the default set of HTTPS // versions the proxy should accept. HTTP versions are @@ -1099,8 +1097,6 @@ func Defaults() Parameters { }, EnvoyServiceName: "envoy", EnvoyServiceNamespace: contourNamespace, - EnvoyIngressName: "envoy", - EnvoyIngressNamespace: contourNamespace, DefaultHTTPVersions: []HTTPVersionType{}, Cluster: ClusterParameters{ DNSLookupFamily: AutoClusterDNSFamily, diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index f985ae33037..77ffeaa17d1 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -6714,6 +6714,24 @@

EnvoyConfig +loadBalancer +
+ +string + + + +(Optional) +

LoadBalancer specifies how Contour should set the ingress status address. +If provided, the value can be in one of the formats: +- address:: Contour will use the provided comma separated list of addresses directly. The address can be a fully qualified domain name or an IP address. +- service:/: Contour will use the address of the designated service. +- ingress:/: Contour will use the address of the designated ingress.

+

Contour’s default is an empty string.

+ + + + http
diff --git a/site/content/docs/main/configuration.md b/site/content/docs/main/configuration.md index 7fd5b5c260f..74a616098b5 100644 --- a/site/content/docs/main/configuration.md +++ b/site/content/docs/main/configuration.md @@ -16,52 +16,53 @@ The `contour serve` command is the main command which is used to watch for Kuber There are a number of flags that can be passed to this command which further configures how Contour operates. Many of these flags are mirrored in the [Contour Configuration File](#configuration-file). -| Flag Name | Description | -| --------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| `--config-path` | Path to base configuration | -| `--contour-config-name` | Name of the ContourConfiguration resource to use | -| `--incluster` | Use in cluster configuration | -| `--kubeconfig=` | Path to kubeconfig (if not in running inside a cluster) | -| `--xds-address=` | xDS gRPC API address | -| `--xds-port=` | xDS gRPC API port | -| `--stats-address=` | Envoy /stats interface address | -| `--stats-port=` | Envoy /stats interface port | -| `--debug-http-address=
` | Address the debug http endpoint will bind to. | -| `--debug-http-port=` | Port the debug http endpoint will bind to | -| `--http-address=` | Address the metrics HTTP endpoint will bind to | -| `--http-port=` | Port the metrics HTTP endpoint will bind to. | -| `--health-address=` | Address the health HTTP endpoint will bind to | -| `--health-port=` | Port the health HTTP endpoint will bind to | -| `--contour-cafile=` | CA bundle file name for serving gRPC with TLS | -| `--contour-cert-file=` | Contour certificate file name for serving gRPC over TLS | -| `--contour-key-file=` | Contour key file name for serving gRPC over TLS | -| `--insecure` | Allow serving without TLS secured gRPC | -| `--root-namespaces=` | Restrict contour to searching these namespaces for root ingress routes | -| `--watch-namespaces=` | Restrict contour to searching these namespaces for all resources | -| `--ingress-class-name=` | Contour IngressClass name (comma-separated list allowed) | -| `--ingress-status-address=
` | Address to set in Ingress object status | -| `--envoy-http-access-log=` | Envoy HTTP access log | -| `--envoy-https-access-log=` | Envoy HTTPS access log | -| `--envoy-service-http-address=` | Kubernetes Service address for HTTP requests | -| `--envoy-service-https-address=` | Kubernetes Service address for HTTPS requests | -| `--envoy-service-http-port=` | Kubernetes Service port for HTTP requests | -| `--envoy-service-https-port=` | Kubernetes Service port for HTTPS requests | -| `--envoy-service-name=` | Name of the Envoy service to inspect for Ingress status details. | -| `--envoy-service-namespace=` | Envoy Service Namespace | -| `--use-proxy-protocol` | Use PROXY protocol for all listeners | -| `--accesslog-format=` | Format for Envoy access logs | -| `--disable-leader-election` | Disable leader election mechanism | -| `--disable-feature=` | Do not start an informer for the specified resources. Flag can be given multiple times. | -| `--leader-election-lease-duration` | The duration of the leadership lease. | -| `--leader-election-renew-deadline` | The duration leader will retry refreshing leadership before giving up. | -| `--leader-election-retry-period` | The interval which Contour will attempt to acquire leadership lease. | -| `--leader-election-resource-name` | The name of the resource (Lease) leader election will lease. | -| `--leader-election-resource-namespace` | The namespace of the resource (Lease) leader election will lease. | -| `-d, --debug` | Enable debug logging | -| `--kubernetes-debug=` | Enable Kubernetes client debug logging | -| `--log-format=` | Log output format for Contour. Either text (default) or json. | -| `--kubernetes-client-qps=` | QPS allowed for the Kubernetes client. | -| `--kubernetes-client-burst=` | Burst allowed for the Kubernetes client. | +| Flag Name | Description | +|----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| +| `--config-path` | Path to base configuration | +| `--contour-config-name` | Name of the ContourConfiguration resource to use | +| `--incluster` | Use in cluster configuration | +| `--kubeconfig=` | Path to kubeconfig (if not in running inside a cluster) | +| `--xds-address=` | xDS gRPC API address | +| `--xds-port=` | xDS gRPC API port | +| `--stats-address=` | Envoy /stats interface address | +| `--stats-port=` | Envoy /stats interface port | +| `--debug-http-address=
` | Address the debug http endpoint will bind to. | +| `--debug-http-port=` | Port the debug http endpoint will bind to | +| `--http-address=` | Address the metrics HTTP endpoint will bind to | +| `--http-port=` | Port the metrics HTTP endpoint will bind to. | +| `--health-address=` | Address the health HTTP endpoint will bind to | +| `--health-port=` | Port the health HTTP endpoint will bind to | +| `--contour-cafile=` | CA bundle file name for serving gRPC with TLS | +| `--contour-cert-file=` | Contour certificate file name for serving gRPC over TLS | +| `--contour-key-file=` | Contour key file name for serving gRPC over TLS | +| `--insecure` | Allow serving without TLS secured gRPC | +| `--root-namespaces=` | Restrict contour to searching these namespaces for root ingress routes | +| `--watch-namespaces=` | Restrict contour to searching these namespaces for all resources | +| `--ingress-class-name=` | Contour IngressClass name (comma-separated list allowed) | +| `--ingress-status-address=
` | Address to set in Ingress object status | +| `--envoy-http-access-log=` | Envoy HTTP access log | +| `--envoy-https-access-log=` | Envoy HTTPS access log | +| `--envoy-service-http-address=` | Kubernetes Service address for HTTP requests | +| `--envoy-service-https-address=` | Kubernetes Service address for HTTPS requests | +| `--envoy-service-http-port=` | Kubernetes Service port for HTTP requests | +| `--envoy-service-https-port=` | Kubernetes Service port for HTTPS requests | +| `--envoy-service-name=` | Name of the Envoy service to inspect for Ingress status details. | +| `--envoy-service-namespace=` | Envoy Service Namespace | +| `--use-proxy-protocol` | Use PROXY protocol for all listeners | +| `--accesslog-format=` | Format for Envoy access logs | +| `--disable-leader-election` | Disable leader election mechanism | +| `--disable-feature=` | Do not start an informer for the specified resources. Flag can be given multiple times. | +| `--leader-election-lease-duration` | The duration of the leadership lease. | +| `--leader-election-renew-deadline` | The duration leader will retry refreshing leadership before giving up. | +| `--leader-election-retry-period` | The interval which Contour will attempt to acquire leadership lease. | +| `--leader-election-resource-name` | The name of the resource (Lease) leader election will lease. | +| `--leader-election-resource-namespace` | The namespace of the resource (Lease) leader election will lease. | +| `--load-balancer-status= | Address to set (kind=hostname) or the source to inspect for ingress status (kind=service or ingress). | +| `-d, --debug` | Enable debug logging | +| `--kubernetes-debug=` | Enable Kubernetes client debug logging | +| `--log-format=` | Log output format for Contour. Either text (default) or json. | +| `--kubernetes-client-qps=` | QPS allowed for the Kubernetes client. | +| `--kubernetes-client-burst=` | Burst allowed for the Kubernetes client. | ## Configuration File