@@ -18,6 +18,7 @@ package controllers
18
18
19
19
import (
20
20
"context"
21
+ "strconv"
21
22
22
23
rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
23
24
@@ -36,6 +37,7 @@ import (
36
37
const (
37
38
oauthProxyContainerName = "oauth-proxy"
38
39
oauthProxyVolumeName = "proxy-tls-secret"
40
+ initContainerName = "create-cert"
39
41
)
40
42
41
43
// log is for logging in this package.
@@ -66,17 +68,44 @@ var _ webhook.CustomValidator = &rayClusterWebhook{}
66
68
func (w * rayClusterWebhook ) Default (ctx context.Context , obj runtime.Object ) error {
67
69
rayCluster := obj .(* rayv1.RayCluster )
68
70
69
- if ! ptr .Deref (w .Config .RayDashboardOAuthEnabled , true ) {
70
- return nil
71
- }
71
+ if ptr .Deref (w .Config .RayDashboardOAuthEnabled , true ) {
72
+ rayclusterlog . V ( 2 ). Info ( "Adding OAuth sidecar container" )
73
+ rayCluster . Spec . HeadGroupSpec . Template . Spec . Containers = upsert ( rayCluster . Spec . HeadGroupSpec . Template . Spec . Containers , oauthProxyContainer ( rayCluster ), withContainerName ( oauthProxyContainerName ))
72
74
73
- rayclusterlog . V ( 2 ). Info ( "Adding OAuth sidecar container" )
75
+ rayCluster . Spec . HeadGroupSpec . Template . Spec . Volumes = upsert ( rayCluster . Spec . HeadGroupSpec . Template . Spec . Volumes , oauthProxyTLSSecretVolume ( rayCluster ), withVolumeName ( oauthProxyVolumeName ) )
74
76
75
- rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers , oauthProxyContainer (rayCluster ), withContainerName (oauthProxyContainerName ))
77
+ rayCluster .Spec .HeadGroupSpec .Template .Spec .ServiceAccountName = rayCluster .Name + "-oauth-proxy"
78
+ }
76
79
77
- rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes , oauthProxyTLSSecretVolume (rayCluster ), withVolumeName (oauthProxyVolumeName ))
80
+ if ptr .Deref (w .Config .MTLSEnabled , true ) {
81
+ rayclusterlog .V (2 ).Info ("Adding create-cert Init Containers" )
82
+ // HeadGroupSpec //
83
+ // Append the list of environment variables for the ray-head container
84
+ for _ , envVar := range envVarList () {
85
+ rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers [0 ].Env = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers [0 ].Env , envVar , withEnvVarName (envVar .Name ))
86
+ }
87
+
88
+ // Append the create-cert Init Container
89
+ rayCluster .Spec .HeadGroupSpec .Template .Spec .InitContainers = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .InitContainers , rayHeadInitContainer (rayCluster , w .Config .IngressDomain ), withContainerName (initContainerName ))
90
+
91
+ // Append the CA volumes
92
+ for _ , caVol := range caVolumes (rayCluster ) {
93
+ rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes , caVol , withVolumeName (caVol .Name ))
94
+ }
95
+ // WorkerGroupSpec //
96
+ // Append the list of environment variables for the worker container
97
+ for _ , envVar := range envVarList () {
98
+ rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Containers [0 ].Env = upsert (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Containers [0 ].Env , envVar , withEnvVarName (envVar .Name ))
99
+ }
100
+
101
+ // Append the CA volumes
102
+ for _ , caVol := range caVolumes (rayCluster ) {
103
+ rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Volumes = upsert (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Volumes , caVol , withVolumeName (caVol .Name ))
104
+ }
105
+ // Append the create-cert Init Container
106
+ rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .InitContainers = upsert (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .InitContainers , rayWorkerInitContainer (), withContainerName (initContainerName ))
78
107
79
- rayCluster . Spec . HeadGroupSpec . Template . Spec . ServiceAccountName = rayCluster . Name + "-oauth-proxy"
108
+ }
80
109
81
110
return nil
82
111
}
@@ -117,6 +146,14 @@ func (w *rayClusterWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj r
117
146
allErrors = append (allErrors , validateHeadGroupServiceAccountName (rayCluster )... )
118
147
}
119
148
149
+ // Init Container related errors
150
+ if ptr .Deref (w .Config .MTLSEnabled , true ) {
151
+ allErrors = append (allErrors , validateHeadInitContainer (rayCluster , w )... )
152
+ allErrors = append (allErrors , validateWorkerInitContainer (rayCluster )... )
153
+ allErrors = append (allErrors , validateHeadEnvVars (rayCluster )... )
154
+ allErrors = append (allErrors , validateWorkerEnvVars (rayCluster )... )
155
+ allErrors = append (allErrors , validateCaVolumes (rayCluster )... )
156
+ }
120
157
return warnings , allErrors .ToAggregate ()
121
158
}
122
159
@@ -225,3 +262,169 @@ func oauthProxyTLSSecretVolume(rayCluster *rayv1.RayCluster) corev1.Volume {
225
262
},
226
263
}
227
264
}
265
+
266
+ func initCaVolumeMounts () []corev1.VolumeMount {
267
+ return []corev1.VolumeMount {
268
+ {
269
+ Name : "ca-vol" ,
270
+ MountPath : "/home/ray/workspace/ca" ,
271
+ ReadOnly : true ,
272
+ },
273
+ {
274
+ Name : "server-cert" ,
275
+ MountPath : "/home/ray/workspace/tls" ,
276
+ ReadOnly : false ,
277
+ },
278
+ }
279
+ }
280
+
281
+ func envVarList () []corev1.EnvVar {
282
+ return []corev1.EnvVar {
283
+ {
284
+ Name : "MY_POD_IP" ,
285
+ ValueFrom : & corev1.EnvVarSource {
286
+ FieldRef : & corev1.ObjectFieldSelector {
287
+ FieldPath : "status.podIP" ,
288
+ },
289
+ },
290
+ },
291
+ {
292
+ Name : "RAY_USE_TLS" ,
293
+ Value : "1" ,
294
+ },
295
+ {
296
+ Name : "RAY_TLS_SERVER_CERT" ,
297
+ Value : "/home/ray/workspace/tls/server.crt" ,
298
+ },
299
+ {
300
+ Name : "RAY_TLS_SERVER_KEY" ,
301
+ Value : "/home/ray/workspace/tls/server.key" ,
302
+ },
303
+ {
304
+ Name : "RAY_TLS_CA_CERT" ,
305
+ Value : "/home/ray/workspace/tls/ca.crt" ,
306
+ },
307
+ }
308
+ }
309
+
310
+ func caVolumes (rayCluster * rayv1.RayCluster ) []corev1.Volume {
311
+ return []corev1.Volume {
312
+ {
313
+ Name : "ca-vol" ,
314
+ VolumeSource : corev1.VolumeSource {
315
+ Secret : & corev1.SecretVolumeSource {
316
+ SecretName : `ca-secret-` + rayCluster .Name ,
317
+ },
318
+ },
319
+ },
320
+ {
321
+ Name : "server-cert" ,
322
+ VolumeSource : corev1.VolumeSource {
323
+ EmptyDir : & corev1.EmptyDirVolumeSource {},
324
+ },
325
+ },
326
+ }
327
+ }
328
+
329
+ func rayHeadInitContainer (rayCluster * rayv1.RayCluster , domain string ) corev1.Container {
330
+ rayClientRoute := "rayclient-" + rayCluster .Name + "-" + rayCluster .Namespace + "." + domain
331
+ // Service name for basic interactive
332
+ svcDomain := rayCluster .Name + "-head-svc." + rayCluster .Namespace + ".svc"
333
+
334
+ initContainerHead := corev1.Container {
335
+ Name : "create-cert" ,
336
+ Image : "quay.io/project-codeflare/ray:latest-py39-cu118" ,
337
+ Command : []string {
338
+ "sh" ,
339
+ "-c" ,
340
+ `cd /home/ray/workspace/tls && openssl req -nodes -newkey rsa:2048 -keyout server.key -out server.csr -subj '/CN=ray-head' && printf "authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nsubjectAltName = @alt_names\n[alt_names]\nDNS.1 = 127.0.0.1\nDNS.2 = localhost\nDNS.3 = ${FQ_RAY_IP}\nDNS.4 = $(awk 'END{print $1}' /etc/hosts)\nDNS.5 = ` + rayClientRoute + `\nDNS.6 = ` + svcDomain + `">./domain.ext && cp /home/ray/workspace/ca/* . && openssl x509 -req -CA ca.crt -CAkey ca.key -in server.csr -out server.crt -days 365 -CAcreateserial -extfile domain.ext` ,
341
+ },
342
+ VolumeMounts : initCaVolumeMounts (),
343
+ }
344
+ return initContainerHead
345
+ }
346
+
347
+ func rayWorkerInitContainer () corev1.Container {
348
+ initContainerWorker := corev1.Container {
349
+ Name : "create-cert" ,
350
+ Image : "quay.io/project-codeflare/ray:latest-py39-cu118" ,
351
+ Command : []string {
352
+ "sh" ,
353
+ "-c" ,
354
+ `cd /home/ray/workspace/tls && openssl req -nodes -newkey rsa:2048 -keyout server.key -out server.csr -subj '/CN=ray-head' && printf "authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nsubjectAltName = @alt_names\n[alt_names]\nDNS.1 = 127.0.0.1\nDNS.2 = localhost\nDNS.3 = ${FQ_RAY_IP}\nDNS.4 = $(awk 'END{print $1}' /etc/hosts)">./domain.ext && cp /home/ray/workspace/ca/* . && openssl x509 -req -CA ca.crt -CAkey ca.key -in server.csr -out server.crt -days 365 -CAcreateserial -extfile domain.ext` ,
355
+ },
356
+ VolumeMounts : initCaVolumeMounts (),
357
+ }
358
+ return initContainerWorker
359
+ }
360
+
361
+ func validateHeadInitContainer (rayCluster * rayv1.RayCluster , w * rayClusterWebhook ) field.ErrorList {
362
+ var allErrors field.ErrorList
363
+
364
+ if err := contains (rayCluster .Spec .HeadGroupSpec .Template .Spec .InitContainers , rayHeadInitContainer (rayCluster , w .Config .IngressDomain ), byContainerName ,
365
+ field .NewPath ("spec" , "headGroupSpec" , "template" , "spec" , "initContainers" ),
366
+ "create-cert Init Container is immutable" ); err != nil {
367
+ allErrors = append (allErrors , err )
368
+ }
369
+
370
+ return allErrors
371
+ }
372
+
373
+ func validateWorkerInitContainer (rayCluster * rayv1.RayCluster ) field.ErrorList {
374
+ var allErrors field.ErrorList
375
+
376
+ if err := contains (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .InitContainers , rayWorkerInitContainer (), byContainerName ,
377
+ field .NewPath ("spec" , "workerGroupSpecs" , "0" , "template" , "spec" , "initContainers" ),
378
+ "create-cert Init Container is immutable" ); err != nil {
379
+ allErrors = append (allErrors , err )
380
+ }
381
+
382
+ return allErrors
383
+ }
384
+
385
+ func validateCaVolumes (rayCluster * rayv1.RayCluster ) field.ErrorList {
386
+ var allErrors field.ErrorList
387
+
388
+ for _ , caVol := range caVolumes (rayCluster ) {
389
+ if err := contains (rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes , caVol , byVolumeName ,
390
+ field .NewPath ("spec" , "headGroupSpec" , "template" , "spec" , "volumes" ),
391
+ "ca-vol and server-cert Secret volumes are immutable" ); err != nil {
392
+ allErrors = append (allErrors , err )
393
+ }
394
+ if err := contains (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Volumes , caVol , byVolumeName ,
395
+ field .NewPath ("spec" , "workerGroupSpecs" , "0" , "template" , "spec" , "volumes" ),
396
+ "ca-vol and server-cert Secret volumes are immutable" ); err != nil {
397
+ allErrors = append (allErrors , err )
398
+ }
399
+ }
400
+
401
+ return allErrors
402
+ }
403
+
404
+ func validateHeadEnvVars (rayCluster * rayv1.RayCluster ) field.ErrorList {
405
+ var allErrors field.ErrorList
406
+
407
+ for _ , envVar := range envVarList () {
408
+ if err := contains (rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers [0 ].Env , envVar , byEnvVarName ,
409
+ field .NewPath ("spec" , "headGroupSpec" , "template" , "spec" , "containers" , strconv .Itoa (0 ), "env" ),
410
+ "RAY_TLS related environment variables are immutable" ); err != nil {
411
+ allErrors = append (allErrors , err )
412
+ }
413
+ }
414
+
415
+ return allErrors
416
+ }
417
+
418
+ func validateWorkerEnvVars (rayCluster * rayv1.RayCluster ) field.ErrorList {
419
+ var allErrors field.ErrorList
420
+
421
+ for _ , envVar := range envVarList () {
422
+ if err := contains (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Containers [0 ].Env , envVar , byEnvVarName ,
423
+ field .NewPath ("spec" , "workerGroupSpecs" , "0" , "template" , "spec" , "containers" , strconv .Itoa (0 ), "env" ),
424
+ "RAY_TLS related environment variables are immutable" ); err != nil {
425
+ allErrors = append (allErrors , err )
426
+ }
427
+ }
428
+
429
+ return allErrors
430
+ }
0 commit comments