grpc-xds/control-plane-go/pkg/xds/cds/cluster.go (99 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cds import ( "fmt" "strings" "time" "google.golang.org/protobuf/types/known/wrapperspb" clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" httpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" "github.com/googlecloudplatform/solutions-workshops/grpc-xds/control-plane-go/pkg/xds/tls" ) const ( envoyExtensionsUpstreamsHTTPProtocolOptions = "envoy.extensions.upstreams.http.v3.HttpProtocolOptions" ) var ( // TODO: Make these configurable. healthCheckInterval = durationpb.New(30 * time.Second) healthCheckTimeout = durationpb.New(1 * time.Second) ) // CreateCluster returns a CDS Cluster. // // `edsServiceName` is the resource name to request from EDS (for Clusters that use EDS). // Typically, this is just the CDS Cluster name, but it must be a different name if the CDS // Cluster name uses the `xdstp://` scheme for xDS federation. // // To enable client-side active health checking, provide a `healthCheckProtocol` value of one of // `grpc`, `http`, or `tcp`. If the health check port is different to the serving port, provide // the health check port number too. // // If the health check port is the same as the serving port, you can provide `0` as the value of // `healthCheckPort`. // // `pathOrGRPCService` is the URL path for HTTP health checks, or the gRPC service name for gRPC // health checks. It is ignored for TCP health checks. // // To disable client-side health checking, set `healthCheckProtocol` to an empty string. // // Client-side active health checks are supported by Envoy proxy, but not by gRPC clients. // See https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/service_discovery#on-eventually-consistent-service-discovery // and https://github.com/grpc/grpc/issues/34581 // // TODO: Clean up too many parameters. func CreateCluster(name string, edsServiceName string, namespace string, serviceAccountName string, healthCheckPort uint32, healthCheckProtocol string, healthCheckPathOrGRPCService string, enableTLS bool, requireClientCerts bool) (*clusterv3.Cluster, error) { anyWrappedHTTPProtocolOptions, err := anypb.New(&httpv3.HttpProtocolOptions{ UpstreamProtocolOptions: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_{ ExplicitHttpConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig{ ProtocolConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_Http2ProtocolOptions{ Http2ProtocolOptions: &corev3.Http2ProtocolOptions{}, }, }, }, }) if err != nil { return nil, fmt.Errorf("could not marshall HttpProtocolOptions into Any instance: %w", err) } cluster := clusterv3.Cluster{ Name: name, ClusterDiscoveryType: &clusterv3.Cluster_Type{ Type: clusterv3.Cluster_EDS, }, EdsClusterConfig: &clusterv3.Cluster_EdsClusterConfig{ EdsConfig: &corev3.ConfigSource{ ResourceApiVersion: corev3.ApiVersion_V3, ConfigSourceSpecifier: &corev3.ConfigSource_Ads{ Ads: &corev3.AggregatedConfigSource{}, }, }, ServiceName: edsServiceName, }, ConnectTimeout: &durationpb.Duration{ Seconds: 3, // default is 5s }, // See https://github.com/envoyproxy/envoy/issues/11527 // IgnoreHealthOnHostRemoval: true, TypedExtensionProtocolOptions: map[string]*anypb.Any{ envoyExtensionsUpstreamsHTTPProtocolOptions: anyWrappedHTTPProtocolOptions, }, // See https://github.com/envoyproxy/envoy/issues/11527 IgnoreHealthOnHostRemoval: true, LbPolicy: clusterv3.Cluster_ROUND_ROBIN, } // Client-side active health checks. Implemented by Envoy, but not by gRPC clients. if healthCheckProtocol != "" { cluster.HealthChecks = []*corev3.HealthCheck{createHealthCheck(healthCheckProtocol, healthCheckPort, healthCheckPathOrGRPCService)} if healthCheckPort != 0 { cluster.HealthChecks[0].AltPort = wrapperspb.UInt32(healthCheckPort) } } if enableTLS { upstreamTLSContext := tls.CreateUpstreamTLSContext(namespace, serviceAccountName, requireClientCerts) transportSocket, err := tls.CreateTransportSocket(upstreamTLSContext) if err != nil { return nil, err } cluster.TransportSocket = transportSocket } return &cluster, nil } func createHealthCheck(protocol string, port uint32, pathOrGRPCService string) *corev3.HealthCheck { healthCheck := &corev3.HealthCheck{ AltPort: wrapperspb.UInt32(port), HealthyThreshold: wrapperspb.UInt32(1), Interval: healthCheckInterval, Timeout: healthCheckTimeout, UnhealthyThreshold: wrapperspb.UInt32(1), } if strings.EqualFold(protocol, "grpc") { healthCheck.HealthChecker = &corev3.HealthCheck_GrpcHealthCheck_{ GrpcHealthCheck: &corev3.HealthCheck_GrpcHealthCheck{ ServiceName: pathOrGRPCService, }, } } else if strings.EqualFold(protocol, "http") { healthCheck.HealthChecker = &corev3.HealthCheck_HttpHealthCheck_{ HttpHealthCheck: &corev3.HealthCheck_HttpHealthCheck{ Path: pathOrGRPCService, }, } } else { // TCP fallback healthCheck.HealthChecker = &corev3.HealthCheck_TcpHealthCheck_{ TcpHealthCheck: &corev3.HealthCheck_TcpHealthCheck{}, } } return healthCheck }