Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions helm-chart/kuberay-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,33 @@ operatorCommand: /manager
# Using this option to configure kuberay-operator to comunitcate to Ray head pods by proxying through the Kubernetes API Server.
# useKubernetesProxy: true

# Dashboard URL and TLS configuration via environment variables.
# Set these under env: to configure HTTPS dashboard connections.
#
# KUBERAY_DASHBOARD_DOMAIN_SUFFIX – domain suffix for HTTPS dashboard URLs.
# When set, the operator builds: https://<head-svc>.<namespace>.<suffix>[:<port>]
# The suffix must include any Kubernetes subdomain prefix (e.g. "svc.cluster.local").
# When unset (default), the operator uses the original plain-HTTP behaviour.
# KUBERAY_DASHBOARD_PORT – port appended to the URL when DOMAIN_SUFFIX is set.
# KUBERAY_DASHBOARD_TLS_CA_CERT – path to PEM CA cert for server cert verification.
# KUBERAY_DASHBOARD_TLS_CLIENT_CERT – path to PEM client cert for mTLS.
# KUBERAY_DASHBOARD_TLS_CLIENT_KEY – path to PEM client key for mTLS.
# KUBERAY_DASHBOARD_TLS_SERVER_NAME – override the server name used for TLS cert verification.
# Set when the server cert is issued for a different name than the host in the dashboard URL.
#
# Example:
# env:
# - name: KUBERAY_DASHBOARD_DOMAIN_SUFFIX
# value: "svc.$(CLUSTER_DOMAIN)"
# - name: KUBERAY_DASHBOARD_PORT
# value: "8266"
# - name: KUBERAY_DASHBOARD_TLS_CA_CERT
# value: /etc/emissary/certs/client/ca.pem
# - name: KUBERAY_DASHBOARD_TLS_CLIENT_CERT
# value: /etc/emissary/certs/client/cert.pem
# - name: KUBERAY_DASHBOARD_TLS_CLIENT_KEY
# value: /etc/emissary/certs/client/key.pem

# -- If leaderElectionEnabled is set to true, the KubeRay operator will use leader election for high availability.
leaderElectionEnabled: true

Expand Down
24 changes: 24 additions & 0 deletions ray-operator/controllers/ray/utils/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,30 @@ const (
// If set to true, we will use deterministic name for head pod. Otherwise, the non-deterministic name is used.
ENABLE_DETERMINISTIC_HEAD_POD_NAME = "ENABLE_DETERMINISTIC_HEAD_POD_NAME"

// Dashboard environment variables configure the Ray dashboard URL and TLS settings.
// Set these via the operator Deployment's env section (deployment.yaml / Helm values).
//
// KUBERAY_DASHBOARD_DOMAIN_SUFFIX – domain suffix appended when constructing HTTPS dashboard URLs.
// When set, the operator builds: https://<head-svc>.<namespace>.<suffix>[:<port>]
// The suffix must include any Kubernetes subdomain prefix, e.g. "svc.cluster.local".
// When unset, the operator falls back to the original plain-HTTP behaviour.
// KUBERAY_DASHBOARD_PORT – port appended to the dashboard URL when KUBERAY_DASHBOARD_DOMAIN_SUFFIX is set.
// When unset, no port is appended.
//
// TLS certificate paths (only relevant when KUBERAY_DASHBOARD_DOMAIN_SUFFIX is set):
// KUBERAY_DASHBOARD_TLS_CA_CERT – path to the PEM CA cert used to verify the dashboard server cert.
// KUBERAY_DASHBOARD_TLS_CLIENT_CERT – path to the PEM client cert presented during mTLS handshake.
// KUBERAY_DASHBOARD_TLS_CLIENT_KEY – path to the PEM private key for the client cert.
// All three TLS vars must be set together for mTLS; only KUBERAY_DASHBOARD_TLS_CA_CERT is needed for one-way TLS.
// KUBERAY_DASHBOARD_TLS_SERVER_NAME – overrides the server name used for TLS certificate verification.
// Useful when the server presents a cert issued for a different name than the host in the dashboard URL.
KUBERAY_DASHBOARD_DOMAIN_SUFFIX = "KUBERAY_DASHBOARD_DOMAIN_SUFFIX"
KUBERAY_DASHBOARD_PORT = "KUBERAY_DASHBOARD_PORT"
KUBERAY_DASHBOARD_TLS_CA_CERT = "KUBERAY_DASHBOARD_TLS_CA_CERT"
KUBERAY_DASHBOARD_TLS_CLIENT_CERT = "KUBERAY_DASHBOARD_TLS_CLIENT_CERT"
KUBERAY_DASHBOARD_TLS_CLIENT_KEY = "KUBERAY_DASHBOARD_TLS_CLIENT_KEY"
KUBERAY_DASHBOARD_TLS_SERVER_NAME = "KUBERAY_DASHBOARD_TLS_SERVER_NAME"

// Ray core default configurations
DefaultWorkerRayGcsReconnectTimeoutS = "600"

Expand Down
100 changes: 88 additions & 12 deletions ray-operator/controllers/ray/utils/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package utils
import (
"context"
"crypto/sha1" //nolint:gosec // We are not using this for security purposes
"crypto/tls"
"crypto/x509"
"encoding/base32"
"fmt"
"math"
Expand Down Expand Up @@ -73,6 +75,28 @@ func GetClusterDomainName() string {
return DefaultDomainName
}

// BuildDashboardURL constructs the dashboard URL for a given head service name and namespace.
// If domainSuffix is non-empty, it constructs an HTTPS URL using the custom domain with the format:
//
// https://<headSvcName>.<namespace>.<domainSuffix>[:<port>]
//
// domainSuffix should include any Kubernetes subdomain prefix, e.g.:
//
// "svc.example.com"
// → raycluster-head-svc.my-namespace.svc.example.com:8266
//
// If domainSuffix is empty it falls back to the plain HTTP URL built from fallbackURL
// (backwards-compatible default behaviour).
func BuildDashboardURL(headSvcName, namespace, domainSuffix, port, fallbackURL string) string {
if domainSuffix == "" {
return "http://" + fallbackURL
}
if port != "" {
return fmt.Sprintf("https://%s.%s.%s:%s", headSvcName, namespace, domainSuffix, port)
}
return fmt.Sprintf("https://%s.%s.%s", headSvcName, namespace, domainSuffix)
}

// IsCreated returns true if pod has been created and is maintained by the API server
func IsCreated(pod *corev1.Pod) bool {
return pod.Status.Phase != ""
Expand Down Expand Up @@ -943,6 +967,55 @@ func FetchHeadServiceURL(ctx context.Context, cli client.Client, rayCluster *ray
return headServiceURL, nil
}

// newDashboardHTTPClient builds an *http.Client for dashboard connections.
// When the KUBERAY_DASHBOARD_TLS_* environment variables are set it configures
// TLS (one-way) or mTLS (when client cert+key are also provided); otherwise it
// returns a plain client suitable for plain HTTP connections.
func newDashboardHTTPClient() (*http.Client, error) {
caFile := os.Getenv(KUBERAY_DASHBOARD_TLS_CA_CERT)
certFile := os.Getenv(KUBERAY_DASHBOARD_TLS_CLIENT_CERT)
keyFile := os.Getenv(KUBERAY_DASHBOARD_TLS_CLIENT_KEY)

if caFile == "" && certFile == "" && keyFile == "" {
return &http.Client{Timeout: 2 * time.Second}, nil
}

if (certFile == "") != (keyFile == "") {
return nil, fmt.Errorf("%s and %s must both be set for mTLS", KUBERAY_DASHBOARD_TLS_CLIENT_CERT, KUBERAY_DASHBOARD_TLS_CLIENT_KEY)
}

tlsCfg := &tls.Config{}

if sn := os.Getenv(KUBERAY_DASHBOARD_TLS_SERVER_NAME); sn != "" {
tlsCfg.ServerName = sn
}

if caFile != "" {
caPEM, err := os.ReadFile(caFile)
if err != nil {
return nil, fmt.Errorf("failed to read dashboard CA cert %q: %w", caFile, err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caPEM) {
return nil, fmt.Errorf("failed to parse dashboard CA cert %q", caFile)
}
tlsCfg.RootCAs = pool
}

if certFile != "" {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("failed to load dashboard client cert/key (%q, %q): %w", certFile, keyFile, err)
}
tlsCfg.Certificates = []tls.Certificate{cert}
}

return &http.Client{
Timeout: 2 * time.Second,
Transport: &http.Transport{TLSClientConfig: tlsCfg},
}, nil
}

func GetRayDashboardClientFunc(mgr manager.Manager, useKubernetesProxy bool) func(rayCluster *rayv1.RayCluster, url string) (dashboardclient.RayDashboardClientInterface, error) {
return func(rayCluster *rayv1.RayCluster, url string) (dashboardclient.RayDashboardClientInterface, error) {
dashboardClient := &dashboardclient.RayDashboardClient{}
Expand All @@ -969,14 +1042,9 @@ func GetRayDashboardClientFunc(mgr manager.Manager, useKubernetesProxy bool) fun
}

if useKubernetesProxy {
var err error
headSvcName := rayCluster.Status.Head.ServiceName
if headSvcName == "" {
headSvcName, err = GenerateHeadServiceName(RayClusterCRD, rayCluster.Spec, rayCluster.Name)
if err != nil {
err = fmt.Errorf("failed to construct Ray dashboard client: %w", err)
return nil, err
}
headSvcName, err := GenerateHeadServiceName(RayClusterCRD, rayCluster.Spec, rayCluster.Name)
if err != nil {
return nil, fmt.Errorf("failed to construct Ray dashboard client: %w", err)
}

dashboardClient.InitClient(
Expand All @@ -989,11 +1057,19 @@ func GetRayDashboardClientFunc(mgr manager.Manager, useKubernetesProxy bool) fun
return dashboardClient, nil
}

headSvcName, err := GenerateHeadServiceName(RayClusterCRD, rayCluster.Spec, rayCluster.Name)
if err != nil {
return nil, fmt.Errorf("failed to construct Ray dashboard client: %w", err)
}

httpClient, err := newDashboardHTTPClient()
if err != nil {
return nil, fmt.Errorf("failed to construct Ray dashboard client: %w", err)
}

dashboardClient.InitClient(
&http.Client{
Timeout: 2 * time.Second,
},
"http://"+url,
httpClient,
BuildDashboardURL(headSvcName, rayCluster.Namespace, os.Getenv(KUBERAY_DASHBOARD_DOMAIN_SUFFIX), os.Getenv(KUBERAY_DASHBOARD_PORT), url),
authToken,
)

Expand Down
Loading