This is an automated email from the ASF dual-hosted git repository.
viirya pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 51d08946ae0c [SPARK-58778][DOC] Document authority-based routing for
Spark Connect behind a shared ingress
51d08946ae0c is described below
commit 51d08946ae0c5adcef2bd3f7681458b2ad86b007
Author: Liang-Chi Hsieh <[email protected]>
AuthorDate: Tue Aug 18 11:14:46 2026 -0700
[SPARK-58778][DOC] Document authority-based routing for Spark Connect
behind a shared ingress
### What changes were proposed in this pull request?
Adds a section to the Spark Connect overview docs
(`docs/spark-connect-overview.md`) explaining how to route a Spark Connect
client to the right server when several services share one hostname behind a
Kubernetes Ingress or reverse proxy. It documents routing on the HTTP/2
`:authority` (via the `grpc.default_authority` channel option) with proxy
host-based routing, instead of a URL path prefix, and covers the TLS case
(keeping the certificate-verification name and the routing tag sepa [...]
This documents an approach that came out of the discussion on #56933 (which
proposed a path-prefix-based `PathAwareChannelBuilder`). Linking it here so
others hitting the same "route Spark Connect behind a shared ingress" need can
find the `:authority`-based approach without re-deriving it.
### Why are the changes needed?
In gRPC the method name *is* the HTTP/2 `:path`
(`/spark.connect.SparkConnectService/ExecutePlan`), so a URL path prefix cannot
be used to route to different Spark Connect services — a prefixed request
reaches the server as an unknown method and returns `UNIMPLEMENTED`. Routing on
`:authority` is the mechanism the gRPC maintainers recommend for this scenario
([grpc/grpc#14900](https://github.com/grpc/grpc/issues/14900)), but it was not
documented for Spark Connect.
### Does this PR introduce _any_ user-facing change?
No. Documentation only.
### How was this patch tested?
Documentation only. The routing approach was verified end-to-end against a
local ingress-nginx setup, over both plaintext (h2c) and TLS.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code
Closes #58011 from viirya/connect-authority-routing-doc.
Authored-by: Liang-Chi Hsieh <[email protected]>
Signed-off-by: Liang-Chi Hsieh <[email protected]>
---
docs/spark-connect-overview.md | 78 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 78 insertions(+)
diff --git a/docs/spark-connect-overview.md b/docs/spark-connect-overview.md
index 02b9318ad415..67f8766f59ce 100644
--- a/docs/spark-connect-overview.md
+++ b/docs/spark-connect-overview.md
@@ -481,3 +481,81 @@ APIs such as
[SparkContext](api/scala/org/apache/spark/SparkContext.html)
and [RDD](api/scala/org/apache/spark/rdd/RDD.html) are unsupported in Spark
Connect.
Support for more APIs is planned for upcoming Spark releases.
+
+# Routing through a shared ingress or reverse proxy
+
+When several services share a single hostname behind a Kubernetes Ingress (or
another
+reverse proxy), it is tempting to give each service a URL path prefix (for
example
+`sc://host/sparkConnect`) and route on that path. This does not work for gRPC:
the
+gRPC method name *is* the HTTP/2 `:path`
(`/spark.connect.SparkConnectService/ExecutePlan`),
+so a connection string cannot carry a separate routing path. Prepending a
prefix to the
+`:path` produces an unknown method and the server responds with
`UNIMPLEMENTED`, unless the
+proxy is configured to strip the prefix back off before forwarding, a
two-sided contract
+that is not part of gRPC's design.
+
+The routing dimension that *is* free is the HTTP/2 `:authority` (the virtual
host). Set it
+to a routing tag with the `grpc.default_authority` channel option, and route
on it at the
+proxy (for example, an Ingress `host:` rule). The client still dials the
shared hostname
+(`default_authority` only overrides the `:authority` header used for routing,
not the address
+it connects to), and the gRPC method `:path` is never touched, so no path
rewrite is needed.
+This is the approach the gRPC maintainers recommend for this scenario
+([grpc/grpc#14900](https://github.com/grpc/grpc/issues/14900)).
+
+The example below uses the Python client, which exposes gRPC channel options
directly:
+
+{% highlight python %}
+from pyspark.sql.connect.session import SparkSession
+from pyspark.sql.connect.client import DefaultChannelBuilder
+
+cb = DefaultChannelBuilder("sc://myhost.com:443")
+cb.setChannelOption("grpc.default_authority", "sparkconnect") # routing tag
+spark = SparkSession.builder.channelBuilder(cb).getOrCreate()
+{% endhighlight %}
+
+A corresponding Kubernetes Ingress routes on that tag and keeps the service at
path `/`
+(no subpath, no rewrite). Note the routing tag must be a lowercase
+[RFC 1123](https://datatracker.ietf.org/doc/html/rfc1123) name, since a
Kubernetes Ingress
+`host:` requires one:
+
+{% highlight yaml %}
+spec:
+ rules:
+ - host: sparkconnect # matches grpc.default_authority
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: spark-connect-server
+ port: { number: 15002 }
+{% endhighlight %}
+
+**Over TLS**, gRPC uses the `:authority` as the certificate-verification name
by default,
+so overriding it with a routing tag would fail verification (the tag is not in
the server
+certificate's SAN). Keep the two names separate: set
`grpc.ssl_target_name_override` to the
+real server hostname (used for certificate verification and SNI) and
`grpc.default_authority`
+to the routing tag.
+
+{% highlight python %}
+cb = DefaultChannelBuilder("sc://myhost.com:443/;use_ssl=true")
+cb.setChannelOption("grpc.ssl_target_name_override", "myhost.com") #
certificate verification / SNI
+cb.setChannelOption("grpc.default_authority", "sparkconnect") # routing
tag
+spark = SparkSession.builder.channelBuilder(cb).getOrCreate()
+{% endhighlight %}
+
+`use_ssl=true` verifies the server certificate against the system's trusted CA
store. If your
+gateway's certificate is issued by a CA the client does not trust by default
(a self-signed or
+internal CA), make that CA trusted on the client side (for example, via
+`GRPC_DEFAULT_SSL_ROOTS_FILE_PATH`) rather than through the connection string.
Configuring the
+proxy's own TLS (which certificate it presents for which hostname) is part of
your ingress
+setup and is out of scope here.
+
+Two notes on `grpc.ssl_target_name_override`:
+gRPC documents it as testing-oriented because its typical misuse is to *mask*
a certificate
+name mismatch (verifying against a name the server does not actually present,
which defeats
+hostname verification). Here it is set to the real, verified hostname, so the
certificate is
+still checked correctly; it only prevents the routing tag from being used as
the verification
+name. If you prefer to avoid the option entirely, issue the server certificate
with the routing
+tag included in its SAN; then `grpc.default_authority` alone is sufficient and
no override is
+needed.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]