-
Notifications
You must be signed in to change notification settings - Fork 314
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support enableIngress for RayCluster (#38)
* Support enableIngress for RayCluster Add ingress resources in role Copy more configurations from cluster annotation Add ingress example Update ingress version from v1beta1 to v1 * Update to expose dashboard only
- Loading branch information
Showing
7 changed files
with
368 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
## Ingress Usage | ||
|
||
### Prerequisite | ||
|
||
It's user's responsibility to install ingress controller by themselves. Technically, any ingress controller implementation should work well. | ||
|
||
In order to pass through the customized ingress configuration, you can annotate `RayCluster` object and controller will pass to ingress object. | ||
|
||
`kubernetes.io/ingress.class` is recommended. | ||
|
||
> Note: If the ingressClassName is omitted, a default Ingress class should be defined. Please make sure default ingress class is created. | ||
``` | ||
apiVersion: ray.io/v1alpha1 | ||
kind: RayCluster | ||
metadata: | ||
annotations: | ||
kubernetes.io/ingress.class: nginx -> this is required | ||
spec: | ||
rayVersion: '1.9.2' | ||
headGroupSpec: | ||
serviceType: NodePort | ||
enableIngress: true -> enables ingress | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
apiVersion: ray.io/v1alpha1 | ||
kind: RayCluster | ||
metadata: | ||
annotations: | ||
kubernetes.io/ingress.class: nginx | ||
name: raycluster-ingress | ||
spec: | ||
rayVersion: '1.6.0' # should match the Ray version in the image of the containers | ||
headGroupSpec: | ||
serviceType: NodePort | ||
enableIngress: true | ||
replicas: 1 | ||
rayStartParams: | ||
port: '6379' | ||
redis-password: 'LetMeInRay' | ||
dashboard-host: '0.0.0.0' | ||
num-cpus: '1' # can be auto-completed from the limits | ||
node-ip-address: $MY_POD_IP # auto-completed as the head pod IP | ||
#pod template | ||
template: | ||
spec: | ||
containers: | ||
- name: ray-head | ||
image: rayproject/ray:1.9.2 | ||
env: | ||
- name: MY_POD_IP | ||
valueFrom: | ||
fieldRef: | ||
fieldPath: status.podIP | ||
ports: | ||
- containerPort: 6379 | ||
name: redis | ||
- containerPort: 8265 # Ray dashboard | ||
name: dashboard | ||
- containerPort: 10001 | ||
name: client | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package common | ||
|
||
import ( | ||
"fmt" | ||
|
||
rayiov1alpha1 "github.com/ray-project/kuberay/ray-operator/api/raycluster/v1alpha1" | ||
"github.com/ray-project/kuberay/ray-operator/controllers/utils" | ||
"github.com/sirupsen/logrus" | ||
networkingv1 "k8s.io/api/networking/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
const IngressClassAnnotationKey = "kubernetes.io/ingress.class" | ||
|
||
// BuildIngressForHeadService Builds the ingress for head service dashboard. | ||
// This is used to expose dashboard for external traffic. | ||
func BuildIngressForHeadService(cluster rayiov1alpha1.RayCluster) (*networkingv1.Ingress, error) { | ||
labels := map[string]string{ | ||
RayClusterLabelKey: cluster.Name, | ||
RayIDLabelKey: utils.GenerateIdentifier(cluster.Name, rayiov1alpha1.HeadNode), | ||
} | ||
|
||
// Copy other ingress configuration from cluster annotation | ||
// This is to provide a generic way for user to customize their ingress settings. | ||
annotation := map[string]string{} | ||
for key, value := range cluster.Annotations { | ||
annotation[key] = value | ||
} | ||
|
||
var paths []networkingv1.HTTPIngressPath | ||
pathType := networkingv1.PathTypeExact | ||
servicePorts := getServicePorts(cluster) | ||
dashboardPort := int32(DefaultDashboardPort) | ||
if port, ok := servicePorts["dashboard"]; ok { | ||
dashboardPort = port | ||
} | ||
paths = []networkingv1.HTTPIngressPath{ | ||
networkingv1.HTTPIngressPath{ | ||
Path: "/" + cluster.Name, | ||
PathType: &pathType, | ||
Backend: networkingv1.IngressBackend{ | ||
Service: &networkingv1.IngressServiceBackend{ | ||
Name: utils.GenerateServiceName(cluster.Name), | ||
Port: networkingv1.ServiceBackendPort{ | ||
Number: dashboardPort, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
ingress := &networkingv1.Ingress{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: utils.GenerateServiceName(cluster.Name), | ||
Namespace: cluster.Namespace, | ||
Labels: labels, | ||
Annotations: annotation, | ||
}, | ||
Spec: networkingv1.IngressSpec{ | ||
Rules: []networkingv1.IngressRule{ | ||
{ | ||
IngressRuleValue: networkingv1.IngressRuleValue{ | ||
HTTP: &networkingv1.HTTPIngressRuleValue{ | ||
Paths: paths, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
// Get ingress class name from rayCluster annotations. this is a required field to use ingress. | ||
ingressClassName, ok := cluster.Annotations[IngressClassAnnotationKey] | ||
if !ok { | ||
logrus.Warn(fmt.Sprintf("ingress class annotation is not set for cluster %s/%s", cluster.Namespace, cluster.Name)) | ||
} else { | ||
ingress.Spec.IngressClassName = &ingressClassName | ||
} | ||
|
||
return ingress, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package common | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/ray-project/kuberay/ray-operator/controllers/utils" | ||
|
||
rayiov1alpha1 "github.com/ray-project/kuberay/ray-operator/api/raycluster/v1alpha1" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/utils/pointer" | ||
) | ||
|
||
var instanceWithIngressEnabled = &rayiov1alpha1.RayCluster{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "raycluster-sample", | ||
Namespace: "default", | ||
Annotations: map[string]string{ | ||
IngressClassAnnotationKey: "nginx", | ||
}, | ||
}, | ||
Spec: rayiov1alpha1.RayClusterSpec{ | ||
RayVersion: "1.0", | ||
HeadGroupSpec: rayiov1alpha1.HeadGroupSpec{ | ||
Replicas: pointer.Int32Ptr(1), | ||
Template: corev1.PodTemplateSpec{ | ||
Spec: corev1.PodSpec{ | ||
Containers: []corev1.Container{ | ||
corev1.Container{ | ||
Name: "ray-head", | ||
Image: "rayproject/autoscaler", | ||
Command: []string{"python"}, | ||
Args: []string{"/opt/code.py"}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
var instanceWithIngressEnabledWithoutIngressClass = &rayiov1alpha1.RayCluster{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "raycluster-sample", | ||
Namespace: "default", | ||
}, | ||
Spec: rayiov1alpha1.RayClusterSpec{ | ||
RayVersion: "1.0", | ||
HeadGroupSpec: rayiov1alpha1.HeadGroupSpec{ | ||
Replicas: pointer.Int32Ptr(1), | ||
Template: corev1.PodTemplateSpec{ | ||
Spec: corev1.PodSpec{ | ||
Containers: []corev1.Container{ | ||
corev1.Container{ | ||
Name: "ray-head", | ||
Image: "rayproject/autoscaler", | ||
Command: []string{"python"}, | ||
Args: []string{"/opt/code.py"}, | ||
Env: []corev1.EnvVar{ | ||
corev1.EnvVar{ | ||
Name: "MY_POD_IP", | ||
ValueFrom: &corev1.EnvVarSource{ | ||
FieldRef: &corev1.ObjectFieldSelector{ | ||
FieldPath: "status.podIP", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
// only throw warning message and rely on Kubernetes to assign default ingress class | ||
func TestBuildIngressForHeadServiceWithoutIngressClass(t *testing.T) { | ||
ingress, err := BuildIngressForHeadService(*instanceWithIngressEnabledWithoutIngressClass) | ||
assert.NotNil(t, ingress) | ||
assert.Nil(t, err) | ||
} | ||
|
||
func TestBuildIngressForHeadService(t *testing.T) { | ||
ingress, err := BuildIngressForHeadService(*instanceWithIngressEnabled) | ||
assert.Nil(t, err) | ||
|
||
// check ingress.class annotation | ||
actualResult := ingress.Labels[RayClusterLabelKey] | ||
expectedResult := instanceWithIngressEnabled.Name | ||
if !reflect.DeepEqual(expectedResult, actualResult) { | ||
t.Fatalf("Expected `%v` but got `%v`", expectedResult, actualResult) | ||
} | ||
|
||
actualResult = ingress.Annotations[IngressClassAnnotationKey] | ||
expectedResult = instanceWithIngressEnabled.Annotations[IngressClassAnnotationKey] | ||
if !reflect.DeepEqual(expectedResult, actualResult) { | ||
t.Fatalf("Expected `%v` but got `%v`", expectedResult, actualResult) | ||
} | ||
|
||
// rules count | ||
assert.Equal(t, 1, len(ingress.Spec.Rules)) | ||
|
||
// paths count | ||
expectedPaths := 1 // dashboard only | ||
actualPaths := len(ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths) | ||
if !reflect.DeepEqual(expectedPaths, actualPaths) { | ||
t.Fatalf("Expected `%v` but got `%v`", expectedPaths, actualPaths) | ||
} | ||
|
||
// path names | ||
paths := ingress.Spec.Rules[0].IngressRuleValue.HTTP.Paths | ||
for _, path := range paths { | ||
actualResult = path.Backend.Service.Name | ||
expectedResult = utils.GenerateServiceName(instanceWithIngressEnabled.Name) | ||
|
||
if !reflect.DeepEqual(expectedResult, actualResult) { | ||
t.Fatalf("Expected `%v` but got `%v`", expectedResult, actualResult) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.