Skip to content

Commit 4eb09b5

Browse files
troychiupawelpaszki
authored andcommitted
[apiserver] Start apiserver v2 in apiserver/cmd/main.go (ray-project#3603)
1 parent c90fe60 commit 4eb09b5

File tree

10 files changed

+465
-1
lines changed

10 files changed

+465
-1
lines changed

apiserver/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ RUN go mod download
1414
COPY proto/ proto/
1515
COPY ray-operator/ ray-operator/
1616
COPY apiserver/ apiserver/
17+
COPY apiserversdk/ apiserversdk/
1718

1819
WORKDIR /workspace/apiserver
1920

apiserver/cmd/main.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ import (
2323
"google.golang.org/grpc/reflection"
2424
"google.golang.org/protobuf/encoding/protojson"
2525
"k8s.io/klog/v2"
26+
"sigs.k8s.io/controller-runtime/pkg/client/config"
2627

2728
"github.com/ray-project/kuberay/apiserver/pkg/interceptor"
2829
"github.com/ray-project/kuberay/apiserver/pkg/manager"
2930
"github.com/ray-project/kuberay/apiserver/pkg/server"
3031
"github.com/ray-project/kuberay/apiserver/pkg/swagger"
3132
"github.com/ray-project/kuberay/apiserver/pkg/util"
33+
"github.com/ray-project/kuberay/apiserversdk"
3234
api "github.com/ray-project/kuberay/proto/go_client"
3335
)
3436

@@ -39,6 +41,7 @@ var (
3941
logFile = flag.String("logFilePath", "", "Synchronize logs to local file")
4042
localSwaggerPath = flag.String("localSwaggerPath", "", "Specify the root directory for `*.swagger.json` the swagger files.")
4143
grpcTimeout = flag.Duration("grpc_timeout", util.GRPCServerDefaultTimeout, "gRPC server timeout duration")
44+
enableAPIServerV2 = flag.Bool("enable-api-server-v2", true, "Enable API server V2")
4245
healthy int32
4346
)
4447

@@ -145,7 +148,23 @@ func startHttpProxy() {
145148
registerHttpHandlerFromEndpoint(ctx, api.RegisterRayJobSubmissionServiceHandlerFromEndpoint, "RayJobSubmissionService", runtimeMux)
146149

147150
// Create a top level mux to include both Http gRPC servers and other endpoints like metrics
148-
topMux := http.NewServeMux()
151+
var topMux *http.ServeMux
152+
if *enableAPIServerV2 {
153+
kubernetesConfig, err := config.GetConfig()
154+
if err != nil {
155+
klog.Fatalf("Failed to load kubeconfig: %v", err)
156+
}
157+
158+
topMux, err = apiserversdk.NewMux(apiserversdk.MuxConfig{
159+
KubernetesConfig: kubernetesConfig,
160+
})
161+
if err != nil {
162+
klog.Fatalf("Failed to create API server mux: %v", err)
163+
}
164+
} else {
165+
topMux = http.NewServeMux()
166+
}
167+
149168
// Seems /apis (matches /apis/v1alpha1/clusters) works fine
150169
topMux.Handle("/", runtimeMux)
151170
topMux.Handle("/metrics", promhttp.Handler())

apiserver/deploy/base/insecure/apiserver.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,32 @@ rules:
133133
- get
134134
- list
135135
---
136+
# apiserversdk requires the following permissions to be able to list and proxy services
137+
apiVersion: rbac.authorization.k8s.io/v1
138+
kind: ClusterRole
139+
metadata:
140+
name: allow-service-access
141+
rules:
142+
- apiGroups: [""]
143+
resources: ["services"]
144+
verbs: ["get", "watch", "list"]
145+
- apiGroups: [""]
146+
resources: ["services/proxy"]
147+
verbs: ["get", "watch", "list"]
148+
---
149+
apiVersion: rbac.authorization.k8s.io/v1
150+
kind: ClusterRoleBinding
151+
metadata:
152+
name: allow-service-access
153+
subjects:
154+
- kind: ServiceAccount
155+
name: kuberay-apiserver
156+
namespace: ray-system
157+
roleRef:
158+
kind: ClusterRole
159+
name: allow-service-access
160+
apiGroup: rbac.authorization.k8s.io
161+
---
136162
apiVersion: v1
137163
kind: Namespace
138164
metadata:

apiserver/deploy/base/secure/apiserver.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,32 @@ rules:
164164
- get
165165
- list
166166
---
167+
# apiserversdk requires the following permissions to be able to list and proxy services
168+
apiVersion: rbac.authorization.k8s.io/v1
169+
kind: ClusterRole
170+
metadata:
171+
name: allow-service-access
172+
rules:
173+
- apiGroups: [""]
174+
resources: ["services"]
175+
verbs: ["get", "watch", "list"]
176+
- apiGroups: [""]
177+
resources: ["services/proxy"]
178+
verbs: ["get", "watch", "list"]
179+
---
180+
apiVersion: rbac.authorization.k8s.io/v1
181+
kind: ClusterRoleBinding
182+
metadata:
183+
name: allow-service-access
184+
subjects:
185+
- kind: ServiceAccount
186+
name: kuberay-apiserver
187+
namespace: ray-system
188+
roleRef:
189+
kind: ClusterRole
190+
name: allow-service-access
191+
apiGroup: rbac.authorization.k8s.io
192+
---
167193
apiVersion: v1
168194
kind: Namespace
169195
metadata:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package apiserversdk
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
corev1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
10+
rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
11+
)
12+
13+
func TestCreateCluster(t *testing.T) {
14+
tCtx, err := NewEnd2EndTestingContext(t)
15+
require.NoError(t, err, "No error expected when creating testing context")
16+
rayClient := tCtx.GetRayHttpClient()
17+
rayCluster := &rayv1.RayCluster{
18+
ObjectMeta: metav1.ObjectMeta{
19+
Name: tCtx.GetRayClusterName(),
20+
Namespace: tCtx.GetNamespaceName(),
21+
},
22+
Spec: rayv1.RayClusterSpec{
23+
HeadGroupSpec: rayv1.HeadGroupSpec{
24+
RayStartParams: map[string]string{},
25+
Template: corev1.PodTemplateSpec{
26+
Spec: corev1.PodSpec{
27+
Containers: []corev1.Container{
28+
{Name: "test", Image: "test"},
29+
},
30+
},
31+
},
32+
},
33+
},
34+
}
35+
_, err = rayClient.RayClusters(tCtx.GetNamespaceName()).Create(tCtx.GetCtx(), rayCluster, metav1.CreateOptions{})
36+
require.NoError(t, err)
37+
38+
actualCluster, err := rayClient.RayClusters(tCtx.GetNamespaceName()).Get(tCtx.GetCtx(), tCtx.GetRayClusterName(), metav1.GetOptions{})
39+
require.NoError(t, err)
40+
require.Equal(t, tCtx.GetRayClusterName(), actualCluster.Name)
41+
require.Equal(t, rayCluster.Spec, actualCluster.Spec)
42+
43+
err = rayClient.RayClusters(tCtx.GetNamespaceName()).Delete(tCtx.GetCtx(), tCtx.GetRayClusterName(), metav1.DeleteOptions{})
44+
require.NoError(t, err)
45+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package apiserversdk
2+
3+
const (
4+
RayImage = "rayproject/ray:2.9.0"
5+
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package apiserversdk
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/onsi/gomega"
8+
"github.com/stretchr/testify/require"
9+
corev1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
12+
rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
13+
)
14+
15+
func TestGetRayClusterEvent(t *testing.T) {
16+
tCtx, err := NewEnd2EndTestingContext(t)
17+
require.NoError(t, err, "No error expected when creating testing context")
18+
rayClient := tCtx.GetRayHttpClient()
19+
rayCluster := &rayv1.RayCluster{
20+
ObjectMeta: metav1.ObjectMeta{
21+
Name: tCtx.GetRayClusterName(),
22+
Namespace: tCtx.GetNamespaceName(),
23+
},
24+
Spec: rayv1.RayClusterSpec{
25+
HeadGroupSpec: rayv1.HeadGroupSpec{
26+
RayStartParams: map[string]string{},
27+
Template: corev1.PodTemplateSpec{
28+
Spec: corev1.PodSpec{
29+
Containers: []corev1.Container{
30+
{Name: "test", Image: "test"},
31+
},
32+
},
33+
},
34+
},
35+
},
36+
}
37+
_, err = rayClient.RayClusters(tCtx.GetNamespaceName()).Create(tCtx.GetCtx(), rayCluster, metav1.CreateOptions{})
38+
require.NoError(t, err)
39+
40+
k8sClient := tCtx.GetK8sHttpClient()
41+
g := gomega.NewWithT(t)
42+
g.Eventually(func() bool {
43+
events, err := k8sClient.CoreV1().Events(tCtx.GetNamespaceName()).List(tCtx.GetCtx(), metav1.ListOptions{})
44+
if err != nil {
45+
return false
46+
}
47+
48+
for _, e := range events.Items {
49+
if e.InvolvedObject.Name == tCtx.GetRayClusterName() && e.InvolvedObject.Kind == "RayCluster" {
50+
return true
51+
}
52+
}
53+
return false
54+
}, 10*time.Second, testPollingInterval).Should(gomega.BeTrue(), "Expected to see RayCluster event in the list of events")
55+
56+
err = rayClient.RayClusters(tCtx.GetNamespaceName()).Delete(tCtx.GetCtx(), tCtx.GetRayClusterName(), metav1.DeleteOptions{})
57+
require.NoError(t, err)
58+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package apiserversdk
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
corev1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
10+
rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
11+
)
12+
13+
func TestGetRayClusterProxy(t *testing.T) {
14+
tCtx, err := NewEnd2EndTestingContext(t)
15+
require.NoError(t, err, "No error expected when creating testing context")
16+
rayClient := tCtx.GetRayHttpClient()
17+
rayCluster := &rayv1.RayCluster{
18+
ObjectMeta: metav1.ObjectMeta{
19+
Name: tCtx.GetRayClusterName(),
20+
Namespace: tCtx.GetNamespaceName(),
21+
},
22+
Spec: rayv1.RayClusterSpec{
23+
HeadGroupSpec: rayv1.HeadGroupSpec{
24+
RayStartParams: map[string]string{},
25+
Template: corev1.PodTemplateSpec{
26+
Spec: corev1.PodSpec{
27+
Containers: []corev1.Container{
28+
{
29+
Name: "ray-head",
30+
Image: tCtx.GetRayImage(),
31+
},
32+
},
33+
},
34+
},
35+
},
36+
},
37+
}
38+
_, err = rayClient.RayClusters(tCtx.GetNamespaceName()).Create(tCtx.GetCtx(), rayCluster, metav1.CreateOptions{})
39+
require.NoError(t, err)
40+
41+
// Wait for the Ray cluster's head pod to be ready so that we can access the dashboard
42+
waitForClusterConditions(t, tCtx, tCtx.GetRayClusterName(), []rayv1.RayClusterConditionType{rayv1.HeadPodReady})
43+
44+
k8sClient := tCtx.GetK8sHttpClient()
45+
serviceName := tCtx.GetRayClusterName() + "-head-svc"
46+
r := k8sClient.CoreV1().Services(tCtx.GetNamespaceName()).ProxyGet("http", serviceName, "8265", "", nil)
47+
resp, err := r.DoRaw(tCtx.GetCtx())
48+
require.NoError(t, err)
49+
require.Contains(t, string(resp), "Ray Dashboard")
50+
51+
err = rayClient.RayClusters(tCtx.GetNamespaceName()).Delete(tCtx.GetCtx(), tCtx.GetRayClusterName(), metav1.DeleteOptions{})
52+
require.NoError(t, err)
53+
}

0 commit comments

Comments
 (0)