From 6f429b51c0f2541b006ebef0ef89439b22037d51 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Wed, 8 Jan 2020 14:57:57 -0800 Subject: [PATCH 01/12] A27: xDS-Based Global Load Balancing --- A27-xds-global-load-balancing.md | 426 ++++++++++++++++++++++ A27_graphics/grpc_client_architecture.png | Bin 0 -> 53500 bytes A27_graphics/grpc_client_architecture.svg | 1 + 3 files changed, 427 insertions(+) create mode 100644 A27-xds-global-load-balancing.md create mode 100644 A27_graphics/grpc_client_architecture.png create mode 100644 A27_graphics/grpc_client_architecture.svg diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md new file mode 100644 index 000000000..b9556affc --- /dev/null +++ b/A27-xds-global-load-balancing.md @@ -0,0 +1,426 @@ +A27: xDS-Based Global Load Balancing +---- +* Author(s): Mark D. Roth +* Approver: a11r +* Status: Draft +* Implemented in: (in progress in C-core, Java, and Go) +* Last updated: 2020-01-07 +* Discussion at: (filled after thread exists) + +## Abstract + +gRPC currently supports its own ["grpclb" +protocol](https://github.com/grpc/grpc-proto/blob/master/grpc/lb/v1/load_balancer.proto) +for [look-aside +load-balancing](https://github.com/grpc/grpc/blob/master/doc/load-balancing.md). +However, the popular [Envoy](http://envoyproxy.io) proxy uses the [xDS +API](https://github.com/envoyproxy/data-plane-api/tree/master/envoy/api/v2) +for many types of configuration, including load balancing, and that API +is evolving into a standard that will be used to configure a variety of +data plane software. In order to converge with this industry trend, gRPC +will be moving from its original grpclb protocol to the new xDS protocol. + +The xDS protocol allows configuring a variety of features, including +many that gRPC does not currently support. Over time, we will be slowly +adding support for more of these features in gRPC. We will publish a +gRFC for each set of xDS features as we add support for them. + +This document describes the changes we are making to support global load +balancing features, which is the set of xDS functionality that we are +initially targetting. This affects the gRPC client only; use of xDS in +the gRPC server will come later as part of a separate set of xDS features. + +## Background + +The xDS API is actually a suite of APIs with names of the form "x +Discovery Service", where there are many values for "x" (hence the name +"xDS" for the overall protocol suite). There is not currently a formal +spec for the protocol, but the best reference is at +https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. +Readers of this document should familiarize themselves with the xDS API +before proceeding. + +The xDS APIs are essentially pub/sub mechanisms, where each API allows +subscribing to a different type of configuration resource. In the xDS +API flow, the client uses the following main APIs, in this order: + +- __Listener Discovery Service (LDS)__: Returns `Listener` resources. + Used basically as a convenient root for the gRPC client's configuration. + Points to the RouteConfiguration. +- __Route Discovery Service (RDS)__: Returns `RouteConfiguration` + resources. Provides data used to populate the gRPC service config. Points + to the Cluster. +- __Cluster Discovery Service (CDS)__: Returns `Cluster` resources. + Configures things like load balancing policy and load reporting. Points + to the ClusterLoadAssignment. +- __Endpoint Discovery Service (EDS)__: Returns `ClusterLoadAssignment` + resources. Configures the set of endpoints (backend servers) to load + balance across and may tell the client to drop requests. + +gRPC will support the Aggregate Discovery Service (ADS) variant of xDS, +where all of these resource types are obtained on a single gRPC stream. +However, the different phases of the API flow are still typically +referred to using the separate service names described above. + +### Related Proposals: + +[A24: Load Balancing Policy Configuration](https://github.com/grpc/proposal/blob/master/A24-lb-policy-config.md) + +## Proposal + +### gRPC Client Architecture + +Because xDS handles not only load balancing but also service discovery +and configuration, gRPC will support xDS via both the resolver and LB +policy plugins. Both the resolver and LB policy plugins will need to +communicate with the xDS server, so the functionality for interacting with +the xDS server will be contained within an XdsClient object, which will +be used by both the resolver and the LB policies. + +There will be two separate LB policies for xDS, one to support `Cluster` +data, and another to support `ClusterLoadAssignment` data. + +Note that both the resolvers and the LB policies will have +"experimental" suffixes for their names for now. These suffixes will be +removed when the functionality has proven to be stable. + +![gRPC Client Architecture Diagram](A27_graphics/grpc_client_architecture.png) + +[Link to SVG file](A27_graphics/grpc_client_architecture.svg) + +#### XdsClient and Bootstrap File + +The XdsClient object contains all of the logic for interacting with the +xDS server. The XdsClient object is a purely internal API, not +something exposed to gRPC users, but it is described here as part of +the architecture used to support xDS in the gRPC client. + +The XdsClient object is configured via a bootstrap file. The location +of the bootstrap file is determined via the `GRPC_XDS_BOOTSTRAP` +environment variable. The file is in JSON form, and its contents look +like this: + +``` +{ + // The xDS server to talk to. The value is an array to allow for a + // future change to add support for failing over to a secondary xDS server + // if the primary is down, but for now, only the first entry in the + // array will be used. + xds_servers": [ + { + "server_uri": , + // List of channel creds; client will stop at the first type it + // supports. This field is optional; if not specified, xDS will use + // the same channel creds as the backends, but with any associated + // call creds stripped off. + "channel_creds": [ + { + "type": , + // The "config" field is optional; it may be missing if the + // credential type does not require config parameters. + "config": + } + ] + } + ], + "node": +} +``` + +Initially, the only type of channel creds we support will be +`google_default`. In the future, we will add a general-purpose +mechanism for configuring arbitrary channel creds types with arbitrary +configuration. + +The `node` field will be populated with the [xDS `Node` +proto](https://github.com/envoyproxy/data-plane-api/blob/1adb5d54abb0e28ca409254d26fad1cf5535239b/envoy/api/v2/core/base.proto#L85). +Note that the `build_version` field should not be specified in the +bootstrap file, since that will be populated automatically based on the gRPC +client's version. + +To allow for future changes in a backward-compatible way, unknown fields +in the bootstrap file will be silently ignored. + +#### xds Resolver + +Clients will enable use of xDS by using the "xds" resolver in the +target URI used to create the gRPC channel. For example, a user may +create a channel using the URI "xds:example.com:123" or +"xds:///example.com:123", which will use xDS to establish contact with +the server "example.com:123". The "xds" URI scheme does not support any +authority. + +When the channel attempts to connect, the xds resolver will +instantiate an XdsClient object and use it to send an xDS request for +a `Listener` resource with the name of the server that the client +wishes to connect to (in the example above, "example.com:123"). If the +resulting `Listener` does not include the `RouteConfiguration`, +then the client will also send an xDS request for that resource. +This information will be used to construct the gRPC [service +config](https://github.com/grpc/grpc/blob/master/doc/service_config.md), +which the resolver will return to the channel. + +In particular, the resulting service config will select use of the CDS +LB policy, described below. The configuration of the CDS policy will +indicate which `Cluster` resource will be used. + +Note that the resolver will return an empty list of addresses, because +in the xDS API flow, the addresses are not returned until the +`ClusterLoadAssignment` resource is obtained later. + +The xds resolver will also return a reference to the XdsClient object +that it instantiated. This reference will be passed down to the LB +policies, which is how the resolver and LB policies can share the same +XdsClient object. + +#### CDS LB Policy + +The CDS LB policy will be the top-level LB policy. Its configuration +will be of the following form: + +``` +{ + "cluster": "" +} +``` + +The CDS policy will use the XdsClient object passed in from the xds resolver +to query for the `Cluster` resource with the cluster name from the LB +policy configuration. + +Initially, the CDS policy will support only hard-coded cluster names, so +it will always create an EDS policy as the child policy. (In the +future, it will change to dynamically determine which child policy to +create, in order to support features like weighted clusters and aggregate +clusters.) + +#### EDS LB Policy + +The EDS LB policy will be the child of the CDS LB policy. Its +configuration will be of the following form: + +``` +{ + "edsServiceName": "", + // Optional; if not specified, load reporting will be disabled. + // If value is the empty string, that means to use the xDS server as + // the LRS server. (Currently, we do not support non-empty values; + // support for this will be added later.) + "lrsLoadReportingServerName": "" +} +``` + +The EDS policy will use the XdsClient object passed in from the xds +resolver to query for the `ClusterLoadAssignment` resource with the +EDS service name from the LB policy configuration. + +Note that unlike the old grpclb policy, which just received a flat list +of backend servers from the balancer to round-robin across, the EDS policy +receives a hierarchical list, where the servers are grouped into +localities, and the localities are grouped into priorities. The EDS +policy will be responsible for both selecting the highest priority set +of localities that are reachable and distributing the traffic across the +localities in the selected priority based on the locality weights. + +Note that the EDS policy will *not* support overprovisioning, which is +different from Envoy. Envoy takes the overprovisioning into account in +both [locality-weighted load +balancing](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/locality_weight) +and [priority +fail-over](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/priority), +but gRPC assumes that the xDS server will update it to redirect traffic +when this kind of graceful fail-over is needed. + +Unlike the grpclb policy, which relied on server-side load reporting to +the balancer, the EDS policy will perform client-side load reporting +using the [LRS +protocol](https://github.com/envoyproxy/data-plane-api/blob/master/envoy/service/load_stats/v2/lrs.proto). +Note that the EDS policy will not support per-endpoint stats; it will +report only per-locality stats. Currently, the EDS policy will report +only client-side stats; in the future, we will support also reporting +backend server stats reported to the client via +[ORCA](https://docs.google.com/document/d/1NSnK3346BkBo1JUU3I9I5NYYnaJZQPt8_Z_XCBCI3uA/edit). + +### How gRPC Will Use the xDS API + +gRPC does not have exactly the same set of features as Envoy, so there +are by necessity some small differences between how the two clients use +the xDS API. This section documents exactly what gRPC requires from the +xDS server and what xDS features it does and does not support. + +Because there are many fields in the xDS APIs that gRPC will not use in +its initial version, the gRPC client must in general ignore any fields +it does not support. This does mean that if we add support for one of +these fields in a future version and users enable use of the feature in +the management server, older clients will continue to ignore the field, +so they may not actually get the new behavior. However, the alternative +is even worse: if we make the client strict (i.e., it will fail if the +management server populates fields that the client does not yet support), +then the same scenario will cause the older clients to fail instead of +just ignoring the new feature. + +Note, however, that there are some unavoidable cases where enabling a +new feature will cause older clients to break. These specific cases +will be called out below. + +#### Initial Request on the ADS Stream + +The first request on the ADS stream will include a Node message that +identifies the client. As mentioned above, most of the fields for the Node +message will be read from the bootstrap file. + +#### LDS + +The gRPC client will typically start by sending an LDS request for a +`Listener` resource whose name matches the server name from the target +URI used to create the gRPC channel (including port suffix if present). +Note that the server name will also be used in the HTTP/2 ":authority" +field, so it needs to be in host or host:port syntax. However, the gRPC +client will not interpret it in any special way. This means that the +xDS server effectively defines what the default port means if the URI +does not specify a port. + +Because we are requesting one specific resource by name, the LDS response +should include exactly one `Listener`. However, many existing xDS servers +do not support requesting one specific resource in LDS, so the gRPC client +should be tolerant of servers that may ignore the requested resource name; +if the server returns multiple resources, the client should look for the one +with the name it asked for, and it should ignore the rest of the entries. +Note that this means that the resource returned by the xDS server must +have exactly the name specified by the client. + +#### Listener Proto + +The returned `Listener` should be an "HTTP API listener", using the format +added to the xDS API in https://github.com/envoyproxy/envoy/pull/8170. +The HTTP API listener configuration may either provide the +`RouteConfiguration` directly in-line, or it may tell the client to +use RDS. Note that if using RDS, the __ConfigSource__ proto for the RDS +server must indicate to use ADS. + +#### RDS + +If the `Listener` does not include the `RouteConfiguration` in-line, +then the client will send an RDS request asking for the specific +`RouteConfiguration` name specified in the `Listener`. + +Because we are requesting one specific resource by name, the RDS response +should include exactly one RouteConfiguration. However, the gRPC client +should be tolerant of servers that may ignore the resource name in the +request; if the server returns multiple resources, the client should look +for the one with the name it asked for, and it should ignore the rest of +the entries. + +#### RouteConfiguration Proto + +The `RouteConfiguration` includes a list of zero or more __VirtualHost__ +resources. The gRPC client will look through this list to find an element +whose domains field matches the server name specified in the "xds:" URI +(with port, if any, stripped off). If no matching VirtualHost is found in +the RouteConfiguration, the xds resolver will return an error to the client +channel. + +In our initial implementation, the only field in the VirtualHost proto +that the gRPC client needs to look at is the list of routes. The client +will look only at the last route in the list (the default route), +whose match field must contain a prefix field whose value is the empty +string and whose route field must be set. Inside that route message, +the cluster field will indicate which cluster to send the request to. + +If the cluster cannot be determined, the xds resolver will return an +error to the client channel. + +If the cluster can be determined, the xds resolver will return a service +config that selects the CDS LB policy. The LB policy's configuration will +include the name of the cluster. + +Notes on backward compatibility: + +- Initially, the gRPC client will look only at the last route in the list, + which is expected to be the default route. In the future, we will add + support for multiple routes, which may be selected based on which RPC + method is being called or possibly on a header match (details TBD). +- Initially, the gRPC client will require that the route action specify a + single cluster, as described above. However, in the future, we will add + support for the `weighted_clusters` field, which will allow us to support + traffic splitting. (This will require introducing a new top-level LB + policy.) +- The `weighted_clusters` field is in the same "oneof" as the `cluster` field. + This means that older clients requiring the `cluster` field will + stop working if the `weighted_clusters` field is specified instead. + Unfortunately, there does not seem to be a way to avoid this, so it's + something that our early users will need to be aware of: when we add + support for `weighted_clusters`, they must upgrade their clients before + they can start using this feature. (We might be able to ameliorate + this by adding support for route matchers at the same time as we + add `weighted_clusters` support, in which case the final route in the + list can continue to use the `cluster` field but earlier routes can + use `weighted_clusters`.) + +#### CDS + +Once the cluster name is obtained from the __VirtualHost__ proto, the gRPC +client will make a CDS request asking for that specific cluster name. + +Because we are requesting one specific resource by name, the CDS response +should include exactly one `Listener`. However, many existing xDS servers +do not support requesting one specific resource in CDS, so the gRPC client +should be tolerant of servers that may ignore the requested resource name; +if the server returns multiple resources, the client should look for the one +with the name it asked for, and it should ignore the rest of the entries. +Note that this means that the resource returned by the xDS server must +have exactly the name specified by the client. + +#### Cluster Proto + +The `Cluster` proto must have the following fields set: + +- The `type` field must be set to EDS. +- In the `eds_cluster_config` field, the `eds_config` field must be set to + indicate to use EDS (and the __ConfigSource__ proto for the EDS server + must indicate to use ADS). If the `service_name` field is set, that value + will be used for the EDS request instead of the cluster name. +- The `lb_policy` field must be set to `ROUND_ROBIN`. +- If the `lrs_server` field is set, it must have its `self` field set, in + which case the client should use LRS for load reporting. Otherwise + (the `lrs_server` field is not set), LRS load reporting will be disabled. + +#### EDS + +After getting the `Cluster` proto, the gRPC client will make an EDS +request for a specific resource name, using either the cluster name or +the value of the `eds_cluster_config.service_name` field in the +`Cluster` proto. + +#### ClusterLoadAssignment Proto + +The `ClusterLoadAssignment` proto must have the following fields set: + +- The `endpoints` field must contain at least one entry. In each entry: + - The `priority` field must be set. + - The `lb_endpoints` field must contain at least one entry. In each entry: + - If the `health_status` field has any value other than HEALTHY or + UNKNOWN, the entry will be ignored. + - The `endpoint` field must be set. Inside of it: + - The `address` field must be set. +- The `policy.drop_overloads` field may be set. + +FIXME: Is this true? Need to resolve internal discussion and decide +whether to remove this before merging this gRFC. +- The `policy.disable_overprovisioning` field must be set to true. + +## Rationale + +This design could have been much simpler if we never intended to +configure anything other than load balancing via xDS, because we could +have done all of the xDS integration inside of a single LB policy, much +like we did with grpclb. However, because we ultimately want to use xDS +to configure things via the service config, we also need to integrate +with xDS via the resolver. + +## Implementation + +We have been working on the implementation of this for a long time (and +it has gone through several design iterations in the process, evolving +as the implementation has progressed). We expect it to be ready in +C-core, Java, and Go in the near future. diff --git a/A27_graphics/grpc_client_architecture.png b/A27_graphics/grpc_client_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..b505a23f7cab5cdd7796314f44202418976af533 GIT binary patch literal 53500 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
    FmY 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 NQv|EZmCBff{4X{}mkR&@ literal 0 HcmV?d00001 diff --git a/A27_graphics/grpc_client_architecture.svg b/A27_graphics/grpc_client_architecture.svg new file mode 100644 index 000000000..7ed2474ad --- /dev/null +++ b/A27_graphics/grpc_client_architecture.svg @@ -0,0 +1 @@ + \ No newline at end of file From 0d2d6c80cee51e483f70df9260ab2fa5b5d34373 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Wed, 8 Jan 2020 15:12:02 -0800 Subject: [PATCH 02/12] Added groups link --- A27-xds-global-load-balancing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index b9556affc..86a11233a 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -5,7 +5,7 @@ A27: xDS-Based Global Load Balancing * Status: Draft * Implemented in: (in progress in C-core, Java, and Go) * Last updated: 2020-01-07 -* Discussion at: (filled after thread exists) +* Discussion at: https://groups.google.com/d/topic/grpc-io/GWIH4XhIqHA/discussion ## Abstract From d70449cb801d4640549b4b6cfc07ccf00c17d6fd Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Thu, 9 Jan 2020 07:41:38 -0800 Subject: [PATCH 03/12] address comments from ejona --- A27-xds-global-load-balancing.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index 86a11233a..c0f79ef07 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -188,11 +188,10 @@ The CDS policy will use the XdsClient object passed in from the xds resolver to query for the `Cluster` resource with the cluster name from the LB policy configuration. -Initially, the CDS policy will support only hard-coded cluster names, so -it will always create an EDS policy as the child policy. (In the -future, it will change to dynamically determine which child policy to -create, in order to support features like weighted clusters and aggregate -clusters.) +Initially, the CDS policy will always create an EDS policy as the child +policy. In the future, this may change; for example, we may add support +for clusters that use DNS or for aggregate clusters, which would require +different child policies. #### EDS LB Policy @@ -363,7 +362,7 @@ Once the cluster name is obtained from the __VirtualHost__ proto, the gRPC client will make a CDS request asking for that specific cluster name. Because we are requesting one specific resource by name, the CDS response -should include exactly one `Listener`. However, many existing xDS servers +should include exactly one `Cluster`. However, many existing xDS servers do not support requesting one specific resource in CDS, so the gRPC client should be tolerant of servers that may ignore the requested resource name; if the server returns multiple resources, the client should look for the one From 613d87eecd3656d3e70b7ca5f7a933d44f9cfe5e Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Thu, 9 Jan 2020 10:56:45 -0800 Subject: [PATCH 04/12] address review comments --- A27-xds-global-load-balancing.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index c0f79ef07..2954b9cbc 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -80,9 +80,9 @@ be used by both the resolver and the LB policies. There will be two separate LB policies for xDS, one to support `Cluster` data, and another to support `ClusterLoadAssignment` data. -Note that both the resolvers and the LB policies will have -"experimental" suffixes for their names for now. These suffixes will be -removed when the functionality has proven to be stable. +Note that the LB policies will have "experimental" suffixes for their +names for now. These suffixes will be removed when the functionality +has proven to be stable. ![gRPC Client Architecture Diagram](A27_graphics/grpc_client_architecture.png) @@ -221,9 +221,10 @@ policy will be responsible for both selecting the highest priority set of localities that are reachable and distributing the traffic across the localities in the selected priority based on the locality weights. -Note that the EDS policy will *not* support overprovisioning, which is -different from Envoy. Envoy takes the overprovisioning into account in -both [locality-weighted load +Note that the EDS policy will *not* support +[overprovisioning](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/overprovisioning), +which is different from Envoy. Envoy takes the +overprovisioning into account in both [locality-weighted load balancing](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/locality_weight) and [priority fail-over](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/priority), @@ -282,9 +283,9 @@ does not specify a port. Because we are requesting one specific resource by name, the LDS response should include exactly one `Listener`. However, many existing xDS servers do not support requesting one specific resource in LDS, so the gRPC client -should be tolerant of servers that may ignore the requested resource name; -if the server returns multiple resources, the client should look for the one -with the name it asked for, and it should ignore the rest of the entries. +must be tolerant of servers that may ignore the requested resource name; +if the server returns multiple resources, the client will look for the one +with the name it asked for, and it will ignore the rest of the entries. Note that this means that the resource returned by the xDS server must have exactly the name specified by the client. @@ -305,9 +306,9 @@ then the client will send an RDS request asking for the specific Because we are requesting one specific resource by name, the RDS response should include exactly one RouteConfiguration. However, the gRPC client -should be tolerant of servers that may ignore the resource name in the -request; if the server returns multiple resources, the client should look -for the one with the name it asked for, and it should ignore the rest of +must be tolerant of servers that may ignore the resource name in the +request; if the server returns multiple resources, the client will look +for the one with the name it asked for, and it will ignore the rest of the entries. #### RouteConfiguration Proto @@ -364,9 +365,9 @@ client will make a CDS request asking for that specific cluster name. Because we are requesting one specific resource by name, the CDS response should include exactly one `Cluster`. However, many existing xDS servers do not support requesting one specific resource in CDS, so the gRPC client -should be tolerant of servers that may ignore the requested resource name; -if the server returns multiple resources, the client should look for the one -with the name it asked for, and it should ignore the rest of the entries. +must be tolerant of servers that may ignore the requested resource name; +if the server returns multiple resources, the client will look for the one +with the name it asked for, and it will ignore the rest of the entries. Note that this means that the resource returned by the xDS server must have exactly the name specified by the client. From e308aba36ed56c689a028c4606cfbe8a304e1dc4 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Thu, 9 Jan 2020 14:15:44 -0800 Subject: [PATCH 05/12] describe intentions w.r.t. xDS protocol variants --- A27-xds-global-load-balancing.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index 2954b9cbc..d041e179f 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -62,6 +62,10 @@ where all of these resource types are obtained on a single gRPC stream. However, the different phases of the API flow are still typically referred to using the separate service names described above. +In the future, we may add support for the incremental ADS variant of +xDS. However, we have no plans to support any non-aggregated variants +of xDS, nor do we plan to support REST. + ### Related Proposals: [A24: Load Balancing Policy Configuration](https://github.com/grpc/proposal/blob/master/A24-lb-policy-config.md) From 8d6eb655fa29d1d4495193bc92f3975a2c6106d7 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Mon, 13 Jan 2020 08:00:09 -0800 Subject: [PATCH 06/12] add locality LB weight field --- A27-xds-global-load-balancing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index d041e179f..9b318e766 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -401,6 +401,8 @@ the value of the `eds_cluster_config.service_name` field in the The `ClusterLoadAssignment` proto must have the following fields set: - The `endpoints` field must contain at least one entry. In each entry: + - If the `load_balancing_weight` field is unset, the `endpoints` entry + is skipped; otherwise, the value is used for weighted locality picking. - The `priority` field must be set. - The `lb_endpoints` field must contain at least one entry. In each entry: - If the `health_status` field has any value other than HEALTHY or From 036a8e0154afe03a4f210c841176a23f10700883 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Wed, 15 Jan 2020 09:01:08 -0800 Subject: [PATCH 07/12] address comments from htuch --- A27-xds-global-load-balancing.md | 70 ++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index 9b318e766..2863a8470 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -44,16 +44,21 @@ The xDS APIs are essentially pub/sub mechanisms, where each API allows subscribing to a different type of configuration resource. In the xDS API flow, the client uses the following main APIs, in this order: -- __Listener Discovery Service (LDS)__: Returns `Listener` resources. - Used basically as a convenient root for the gRPC client's configuration. - Points to the RouteConfiguration. -- __Route Discovery Service (RDS)__: Returns `RouteConfiguration` - resources. Provides data used to populate the gRPC service config. Points - to the Cluster. -- __Cluster Discovery Service (CDS)__: Returns `Cluster` resources. - Configures things like load balancing policy and load reporting. Points - to the ClusterLoadAssignment. -- __Endpoint Discovery Service (EDS)__: Returns `ClusterLoadAssignment` +- __Listener Discovery Service (LDS)__: Returns + [`Listener`](https://github.com/envoyproxy/envoy/blob/9e83625b16851cdc7e4b0a4483b0ce07c33ba76b/api/envoy/api/v2/listener.proto#L27) + resources. Used basically as a convenient root for the gRPC client's + configuration. Points to the RouteConfiguration. +- __Route Discovery Service (RDS)__: Returns + [`RouteConfiguration`](https://github.com/envoyproxy/envoy/blob/9e83625b16851cdc7e4b0a4483b0ce07c33ba76b/api/envoy/api/v2/route.proto#L24) + resources. Provides data used to populate the gRPC [service + config](https://github.com/grpc/grpc/blob/master/doc/service_config.md). + Points to the Cluster. +- __Cluster Discovery Service (CDS)__: Returns + [`Cluster`](https://github.com/envoyproxy/envoy/blob/9e83625b16851cdc7e4b0a4483b0ce07c33ba76b/api/envoy/api/v2/cluster.proto#L34) + resources. Configures things like load balancing policy and load reporting. + Points to the ClusterLoadAssignment. +- __Endpoint Discovery Service (EDS)__: Returns + [`ClusterLoadAssignment`](https://github.com/envoyproxy/envoy/blob/9e83625b16851cdc7e4b0a4483b0ce07c33ba76b/api/envoy/api/v2/endpoint.proto#L33) resources. Configures the set of endpoints (backend servers) to load balance across and may tell the client to drop requests. @@ -147,14 +152,14 @@ in the bootstrap file will be silently ignored. #### xds Resolver -Clients will enable use of xDS by using the "xds" resolver in the +Clients will enable use of xDS by using the `xds` resolver in the target URI used to create the gRPC channel. For example, a user may create a channel using the URI "xds:example.com:123" or "xds:///example.com:123", which will use xDS to establish contact with the server "example.com:123". The "xds" URI scheme does not support any authority. -When the channel attempts to connect, the xds resolver will +When the channel attempts to connect, the `xds` resolver will instantiate an XdsClient object and use it to send an xDS request for a `Listener` resource with the name of the server that the client wishes to connect to (in the example above, "example.com:123"). If the @@ -164,15 +169,15 @@ This information will be used to construct the gRPC [service config](https://github.com/grpc/grpc/blob/master/doc/service_config.md), which the resolver will return to the channel. -In particular, the resulting service config will select use of the CDS -LB policy, described below. The configuration of the CDS policy will -indicate which `Cluster` resource will be used. +The `xds` resolver will return a service config that selects use of the CDS LB +policy, described below. The configuration of the CDS policy will indicate +which `Cluster` resource will be used. -Note that the resolver will return an empty list of addresses, because +Note that the `xds` resolver will return an empty list of addresses, because in the xDS API flow, the addresses are not returned until the `ClusterLoadAssignment` resource is obtained later. -The xds resolver will also return a reference to the XdsClient object +The `xds` resolver will also return a reference to the XdsClient object that it instantiated. This reference will be passed down to the LB policies, which is how the resolver and LB policies can share the same XdsClient object. @@ -188,7 +193,7 @@ will be of the following form: } ``` -The CDS policy will use the XdsClient object passed in from the xds resolver +The CDS policy will use the XdsClient object passed in from the `xds` resolver to query for the `Cluster` resource with the cluster name from the LB policy configuration. @@ -285,7 +290,7 @@ xDS server effectively defines what the default port means if the URI does not specify a port. Because we are requesting one specific resource by name, the LDS response -should include exactly one `Listener`. However, many existing xDS servers +should include at least one `Listener`. However, many existing xDS servers do not support requesting one specific resource in LDS, so the gRPC client must be tolerant of servers that may ignore the requested resource name; if the server returns multiple resources, the client will look for the one @@ -298,22 +303,22 @@ have exactly the name specified by the client. The returned `Listener` should be an "HTTP API listener", using the format added to the xDS API in https://github.com/envoyproxy/envoy/pull/8170. The HTTP API listener configuration may either provide the -`RouteConfiguration` directly in-line, or it may tell the client to +`RouteConfiguration` directly inline, or it may tell the client to use RDS. Note that if using RDS, the __ConfigSource__ proto for the RDS server must indicate to use ADS. #### RDS -If the `Listener` does not include the `RouteConfiguration` in-line, +If the `Listener` does not include the `RouteConfiguration` inline, then the client will send an RDS request asking for the specific `RouteConfiguration` name specified in the `Listener`. Because we are requesting one specific resource by name, the RDS response -should include exactly one RouteConfiguration. However, the gRPC client -must be tolerant of servers that may ignore the resource name in the -request; if the server returns multiple resources, the client will look -for the one with the name it asked for, and it will ignore the rest of -the entries. +should include no more than one RouteConfiguration. However, the gRPC +client must be tolerant of servers that may ignore the resource name in +the request; if the server returns multiple resources, the client will +look for the one with the name it asked for, and it will ignore the rest +of the entries. #### RouteConfiguration Proto @@ -321,7 +326,7 @@ The `RouteConfiguration` includes a list of zero or more __VirtualHost__ resources. The gRPC client will look through this list to find an element whose domains field matches the server name specified in the "xds:" URI (with port, if any, stripped off). If no matching VirtualHost is found in -the RouteConfiguration, the xds resolver will return an error to the client +the RouteConfiguration, the `xds` resolver will return an error to the client channel. In our initial implementation, the only field in the VirtualHost proto @@ -331,10 +336,10 @@ whose match field must contain a prefix field whose value is the empty string and whose route field must be set. Inside that route message, the cluster field will indicate which cluster to send the request to. -If the cluster cannot be determined, the xds resolver will return an +If the cluster cannot be determined, the `xds` resolver will return an error to the client channel. -If the cluster can be determined, the xds resolver will return a service +If the cluster can be determined, the `xds` resolver will return a service config that selects the CDS LB policy. The LB policy's configuration will include the name of the cluster. @@ -367,7 +372,7 @@ Once the cluster name is obtained from the __VirtualHost__ proto, the gRPC client will make a CDS request asking for that specific cluster name. Because we are requesting one specific resource by name, the CDS response -should include exactly one `Cluster`. However, many existing xDS servers +should include at least one `Cluster`. However, many existing xDS servers do not support requesting one specific resource in CDS, so the gRPC client must be tolerant of servers that may ignore the requested resource name; if the server returns multiple resources, the client will look for the one @@ -408,7 +413,10 @@ The `ClusterLoadAssignment` proto must have the following fields set: - If the `health_status` field has any value other than HEALTHY or UNKNOWN, the entry will be ignored. - The `endpoint` field must be set. Inside of it: - - The `address` field must be set. + - The `address` field must be set. Inside of it: + - The `socket_address` field must be set. Inside of it: + - The `address` field must be set to an IPv4 or IPv6 address. + - The `port_value` field must be set. - The `policy.drop_overloads` field may be set. FIXME: Is this true? Need to resolve internal discussion and decide From d1fda485c34f62af49d8167a3dc2cc3ab2c9830d Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Wed, 26 Feb 2020 09:48:24 -0800 Subject: [PATCH 08/12] Update overprovisioning handling. --- A27-xds-global-load-balancing.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index 2863a8470..a5ac44be0 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -143,9 +143,9 @@ configuration. The `node` field will be populated with the [xDS `Node` proto](https://github.com/envoyproxy/data-plane-api/blob/1adb5d54abb0e28ca409254d26fad1cf5535239b/envoy/api/v2/core/base.proto#L85). -Note that the `build_version` field should not be specified in the -bootstrap file, since that will be populated automatically based on the gRPC -client's version. +Note that the `build_version` and `client_features` fields should not be +specified in the bootstrap file, since they will be populated automatically +by the gRPC xDS client. To allow for future changes in a backward-compatible way, unknown fields in the bootstrap file will be silently ignored. @@ -236,9 +236,14 @@ which is different from Envoy. Envoy takes the overprovisioning into account in both [locality-weighted load balancing](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/locality_weight) and [priority -fail-over](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/priority), +failover](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/priority), but gRPC assumes that the xDS server will update it to redirect traffic -when this kind of graceful fail-over is needed. +when this kind of graceful failover is needed. gRPC will send the +[`envoy.lb.does_not_support_overprovisioning` client +feature](https://github.com/envoyproxy/envoy/pull/10136) to the xDS +server to tell the xDS server that it will not perform graceful failover; +xDS server implementations may use this to decide whether to perform +graceful failover themselves. Unlike the grpclb policy, which relied on server-side load reporting to the balancer, the EDS policy will perform client-side load reporting @@ -418,10 +423,8 @@ The `ClusterLoadAssignment` proto must have the following fields set: - The `address` field must be set to an IPv4 or IPv6 address. - The `port_value` field must be set. - The `policy.drop_overloads` field may be set. - -FIXME: Is this true? Need to resolve internal discussion and decide -whether to remove this before merging this gRFC. -- The `policy.disable_overprovisioning` field must be set to true. +- Note: The `policy.overprovisioning_factor` field will be ignored if + set; see description of the EDS LB policy above for details. ## Rationale From b5f381eb7a56b0b936210a6cc7a4c308bac806db Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Fri, 28 Feb 2020 10:04:54 -0800 Subject: [PATCH 09/12] node user_agent_name and user_agent_version fields --- A27-xds-global-load-balancing.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index a5ac44be0..8877d6623 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -143,9 +143,9 @@ configuration. The `node` field will be populated with the [xDS `Node` proto](https://github.com/envoyproxy/data-plane-api/blob/1adb5d54abb0e28ca409254d26fad1cf5535239b/envoy/api/v2/core/base.proto#L85). -Note that the `build_version` and `client_features` fields should not be -specified in the bootstrap file, since they will be populated automatically -by the gRPC xDS client. +Note that the `build_version`, `user_agent_name`, `user_agent_version`, and +`client_features` fields should not be specified in the bootstrap file, since +they will be populated automatically by the gRPC xDS client. To allow for future changes in a backward-compatible way, unknown fields in the bootstrap file will be silently ignored. From 340c6ca353a020df2114a6c7293487f1c21fc0cc Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Thu, 12 Mar 2020 14:28:43 -0700 Subject: [PATCH 10/12] we now include port when matching domains --- A27-xds-global-load-balancing.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index 8877d6623..6420c4c01 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -329,10 +329,9 @@ of the entries. The `RouteConfiguration` includes a list of zero or more __VirtualHost__ resources. The gRPC client will look through this list to find an element -whose domains field matches the server name specified in the "xds:" URI -(with port, if any, stripped off). If no matching VirtualHost is found in -the RouteConfiguration, the `xds` resolver will return an error to the client -channel. +whose domains field matches the server name specified in the "xds:" URI. +If no matching VirtualHost is found in the RouteConfiguration, the `xds` +resolver will return an error to the client channel. In our initial implementation, the only field in the VirtualHost proto that the gRPC client needs to look at is the list of routes. The client From 9b1dd39ecfa4435db2ff7e2e7c5d0c761357b691 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Wed, 18 Mar 2020 11:22:29 -0700 Subject: [PATCH 11/12] add note about filesystem subscriptions and xDS API version --- A27-xds-global-load-balancing.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index 6420c4c01..9a0c21508 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -69,7 +69,11 @@ referred to using the separate service names described above. In the future, we may add support for the incremental ADS variant of xDS. However, we have no plans to support any non-aggregated variants -of xDS, nor do we plan to support REST. +of xDS, nor do we plan to support REST or filesystem subscription. + +Initially, gRPC will support version 2 of the xDS APIs. In the future, +we will likely upgrade to v3 or beyond, with appropriate transition +periods to allow users to upgrade their management servers. ### Related Proposals: From 34dee185a28e82b0ac422f4642f6b81862d62e36 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Wed, 18 Mar 2020 11:24:51 -0700 Subject: [PATCH 12/12] update gRFC status and modified date --- A27-xds-global-load-balancing.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/A27-xds-global-load-balancing.md b/A27-xds-global-load-balancing.md index 9a0c21508..004bda6be 100644 --- a/A27-xds-global-load-balancing.md +++ b/A27-xds-global-load-balancing.md @@ -2,9 +2,9 @@ A27: xDS-Based Global Load Balancing ---- * Author(s): Mark D. Roth * Approver: a11r -* Status: Draft -* Implemented in: (in progress in C-core, Java, and Go) -* Last updated: 2020-01-07 +* Status: Implemented +* Implemented in: C-core, Java, and Go +* Last updated: 2020-03-18 * Discussion at: https://groups.google.com/d/topic/grpc-io/GWIH4XhIqHA/discussion ## Abstract