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]

Reply via email to