From 8bc186975adc9090e080db815f706420923179fb Mon Sep 17 00:00:00 2001 From: Mark Rossett Date: Thu, 29 Jan 2026 15:23:09 -0800 Subject: [PATCH 1/5] API chagnes for RayJobSpec.SubmitterContainerTemplate Signed-off-by: Mark Rossett --- docs/reference/api.md | 3 +- ray-operator/apis/ray/v1/rayjob_types.go | 6 + .../apis/ray/v1/zz_generated.deepcopy.go | 5 + .../config/crd/bases/ray.io_raycronjobs.yaml | 720 ++++++++++++++++++ .../config/crd/bases/ray.io_rayjobs.yaml | 720 ++++++++++++++++++ .../applyconfiguration/ray/v1/rayjobspec.go | 14 + 6 files changed, 1467 insertions(+), 1 deletion(-) diff --git a/docs/reference/api.md b/docs/reference/api.md index aef1aef4235..17822f9c675 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -436,7 +436,8 @@ _Appears in:_ | `activeDeadlineSeconds` _integer_ | ActiveDeadlineSeconds is the duration in seconds that the RayJob may be active before
KubeRay actively tries to terminate the RayJob; value must be positive integer. | | | | `backoffLimit` _integer_ | Specifies the number of retries before marking this job failed.
Each retry creates a new RayCluster. | 0 | | | `rayClusterSpec` _[RayClusterSpec](#rayclusterspec)_ | RayClusterSpec is the cluster template to run the job | | | -| `submitterPodTemplate` _[PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#podtemplatespec-v1-core)_ | SubmitterPodTemplate is the template for the pod that will run `ray job submit`. | | | +| `submitterPodTemplate` _[PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#podtemplatespec-v1-core)_ | SubmitterPodTemplate is the template for the pod that will run `ray job submit`.
This is used when SubmissionMode is `K8sJobMode`. | | | +| `submitterContainerTemplate` _[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#container-v1-core)_ | SubmitterContainerTemplate is the template for the sidecar submitter container
injected into the Ray head Pod to run `ray job submit`
This is used when SubmissionMode is `SidecarMode`. | | | | `metadata` _object (keys:string, values:string)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `clusterSelector` _object (keys:string, values:string)_ | clusterSelector is used to select running rayclusters by labels | | | | `submitterConfig` _[SubmitterConfig](#submitterconfig)_ | Configurations of submitter k8s job. | | | diff --git a/ray-operator/apis/ray/v1/rayjob_types.go b/ray-operator/apis/ray/v1/rayjob_types.go index 298da3de91d..b02abc50a32 100644 --- a/ray-operator/apis/ray/v1/rayjob_types.go +++ b/ray-operator/apis/ray/v1/rayjob_types.go @@ -218,8 +218,14 @@ type RayJobSpec struct { // RayClusterSpec is the cluster template to run the job RayClusterSpec *RayClusterSpec `json:"rayClusterSpec,omitempty"` // SubmitterPodTemplate is the template for the pod that will run `ray job submit`. + // This is used when SubmissionMode is `K8sJobMode`. // +optional SubmitterPodTemplate *corev1.PodTemplateSpec `json:"submitterPodTemplate,omitempty"` + // SubmitterContainerTemplate is the template for the sidecar submitter container + // injected into the Ray head Pod to run `ray job submit` + // This is used when SubmissionMode is `SidecarMode`. + // +optional + SubmitterContainerTemplate *corev1.Container `json:"submitterContainerTemplate,omitempty"` // Metadata is data to store along with this job. // +optional Metadata map[string]string `json:"metadata,omitempty"` diff --git a/ray-operator/apis/ray/v1/zz_generated.deepcopy.go b/ray-operator/apis/ray/v1/zz_generated.deepcopy.go index 2adc56438f2..a46c79fd5d5 100644 --- a/ray-operator/apis/ray/v1/zz_generated.deepcopy.go +++ b/ray-operator/apis/ray/v1/zz_generated.deepcopy.go @@ -693,6 +693,11 @@ func (in *RayJobSpec) DeepCopyInto(out *RayJobSpec) { *out = new(corev1.PodTemplateSpec) (*in).DeepCopyInto(*out) } + if in.SubmitterContainerTemplate != nil { + in, out := &in.SubmitterContainerTemplate, &out.SubmitterContainerTemplate + *out = new(corev1.Container) + (*in).DeepCopyInto(*out) + } if in.Metadata != nil { in, out := &in.Metadata, &out.Metadata *out = make(map[string]string, len(*in)) diff --git a/ray-operator/config/crd/bases/ray.io_raycronjobs.yaml b/ray-operator/config/crd/bases/ray.io_raycronjobs.yaml index c2a3f252a89..030624136da 100644 --- a/ray-operator/config/crd/bases/ray.io_raycronjobs.yaml +++ b/ray-operator/config/crd/bases/ray.io_raycronjobs.yaml @@ -8536,6 +8536,726 @@ spec: format: int32 type: integer type: object + submitterContainerTemplate: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + type: string + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object submitterPodTemplate: properties: metadata: diff --git a/ray-operator/config/crd/bases/ray.io_rayjobs.yaml b/ray-operator/config/crd/bases/ray.io_rayjobs.yaml index f4e0260e101..371942a527f 100644 --- a/ray-operator/config/crd/bases/ray.io_rayjobs.yaml +++ b/ray-operator/config/crd/bases/ray.io_rayjobs.yaml @@ -8540,6 +8540,726 @@ spec: format: int32 type: integer type: object + submitterContainerTemplate: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + type: string + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object submitterPodTemplate: properties: metadata: diff --git a/ray-operator/pkg/client/applyconfiguration/ray/v1/rayjobspec.go b/ray-operator/pkg/client/applyconfiguration/ray/v1/rayjobspec.go index 60d69094112..b1d5b048cab 100644 --- a/ray-operator/pkg/client/applyconfiguration/ray/v1/rayjobspec.go +++ b/ray-operator/pkg/client/applyconfiguration/ray/v1/rayjobspec.go @@ -4,6 +4,7 @@ package v1 import ( rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1" + apicorev1 "k8s.io/api/core/v1" corev1 "k8s.io/client-go/applyconfigurations/core/v1" ) @@ -21,7 +22,12 @@ type RayJobSpecApplyConfiguration struct { // RayClusterSpec is the cluster template to run the job RayClusterSpec *RayClusterSpecApplyConfiguration `json:"rayClusterSpec,omitempty"` // SubmitterPodTemplate is the template for the pod that will run `ray job submit`. + // This is used when SubmissionMode is `K8sJobMode`. SubmitterPodTemplate *corev1.PodTemplateSpecApplyConfiguration `json:"submitterPodTemplate,omitempty"` + // SubmitterContainerTemplate is the template for the sidecar submitter container + // injected into the Ray head Pod to run `ray job submit` + // This is used when SubmissionMode is `SidecarMode`. + SubmitterContainerTemplate *apicorev1.Container `json:"submitterContainerTemplate,omitempty"` // Metadata is data to store along with this job. Metadata map[string]string `json:"metadata,omitempty"` // clusterSelector is used to select running rayclusters by labels @@ -113,6 +119,14 @@ func (b *RayJobSpecApplyConfiguration) WithSubmitterPodTemplate(value *corev1.Po return b } +// WithSubmitterContainerTemplate sets the SubmitterContainerTemplate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SubmitterContainerTemplate field is set to the value of the last call. +func (b *RayJobSpecApplyConfiguration) WithSubmitterContainerTemplate(value apicorev1.Container) *RayJobSpecApplyConfiguration { + b.SubmitterContainerTemplate = &value + return b +} + // WithMetadata puts the entries into the Metadata field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, the entries provided by each call will be put on the Metadata field, From 57b7cac6d27b2c99783d65f7cff21c6a7cdde5db Mon Sep 17 00:00:00 2001 From: Mark Rossett Date: Thu, 29 Jan 2026 15:32:53 -0800 Subject: [PATCH 2/5] updating conroller validation for SumitterContainerTemplate Signed-off-by: Mark Rossett --- .../controllers/ray/utils/validation.go | 7 +++- .../controllers/ray/utils/validation_test.go | 42 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/ray-operator/controllers/ray/utils/validation.go b/ray-operator/controllers/ray/utils/validation.go index 50b795d104a..7dcf3f6507b 100644 --- a/ray-operator/controllers/ray/utils/validation.go +++ b/ray-operator/controllers/ray/utils/validation.go @@ -304,7 +304,7 @@ func ValidateRayJobSpec(rayJob *rayv1.RayJob) error { if rayJob.Spec.SubmissionMode == rayv1.SidecarMode { if rayJob.Spec.SubmitterPodTemplate != nil { - return fmt.Errorf("Currently, SidecarMode doesn't support SubmitterPodTemplate") + return fmt.Errorf("SubmitterPodTemplate is not supported in SidecarMode; use SubmitterContainerTemplate instead") } if rayJob.Spec.SubmitterConfig != nil { @@ -316,6 +316,11 @@ func ValidateRayJobSpec(rayJob *rayv1.RayJob) error { } } + // SubmitterContainerTemplate is only supported in SidecarMode + if rayJob.Spec.SubmissionMode != rayv1.SidecarMode && rayJob.Spec.SubmitterContainerTemplate != nil { + return fmt.Errorf("SubmitterContainerTemplate is only supported in SidecarMode; use SubmitterPodTemplate instead") + } + if rayJob.Spec.RayClusterSpec != nil { if err := ValidateRayClusterSpec(rayJob.Spec.RayClusterSpec, rayJob.Annotations); err != nil { return fmt.Errorf("The RayJob spec is invalid: %w", err) diff --git a/ray-operator/controllers/ray/utils/validation_test.go b/ray-operator/controllers/ray/utils/validation_test.go index 36cadc9b260..a1f33f884cc 100644 --- a/ray-operator/controllers/ray/utils/validation_test.go +++ b/ray-operator/controllers/ray/utils/validation_test.go @@ -1106,6 +1106,42 @@ func TestValidateRayJobSpec(t *testing.T) { }, expectError: true, }, + { + name: "SidecarMode supports SubmitterContainerTemplate", + spec: rayv1.RayJobSpec{ + SubmissionMode: rayv1.SidecarMode, + SubmitterContainerTemplate: createBasicSubmitterContainerTemplate(), + RayClusterSpec: createBasicRayClusterSpec(), + }, + expectError: false, + }, + { + name: "K8sJobMode doesn't support SubmitterContainerTemplate", + spec: rayv1.RayJobSpec{ + SubmissionMode: rayv1.K8sJobMode, + SubmitterContainerTemplate: createBasicSubmitterContainerTemplate(), + RayClusterSpec: createBasicRayClusterSpec(), + }, + expectError: true, + }, + { + name: "HTTPMode doesn't support SubmitterContainerTemplate", + spec: rayv1.RayJobSpec{ + SubmissionMode: rayv1.HTTPMode, + SubmitterContainerTemplate: createBasicSubmitterContainerTemplate(), + RayClusterSpec: createBasicRayClusterSpec(), + }, + expectError: true, + }, + { + name: "InteractiveMode doesn't support SubmitterContainerTemplate", + spec: rayv1.RayJobSpec{ + SubmissionMode: rayv1.InteractiveMode, + SubmitterContainerTemplate: createBasicSubmitterContainerTemplate(), + RayClusterSpec: createBasicRayClusterSpec(), + }, + expectError: true, + }, { name: "SidecarMode doesn't support SubmitterConfig", spec: rayv1.RayJobSpec{ @@ -1910,6 +1946,12 @@ func createBasicRayClusterSpec() *rayv1.RayClusterSpec { } } +func createBasicSubmitterContainerTemplate() *corev1.Container { + return &corev1.Container{ + Image: "rayproject/ray:2.52.0", + } +} + func TestValidateClusterUpgradeOptions(t *testing.T) { tests := []struct { maxSurgePercent *int32 From 04cec732c191a22d6d5b24a13de9fa334e26d3df Mon Sep 17 00:00:00 2001 From: Mark Rossett Date: Fri, 30 Jan 2026 13:33:05 -0800 Subject: [PATCH 3/5] Adding support for SubmitterContainerTemplate into rayjob controller Signed-off-by: Mark Rossett --- .../controllers/ray/rayjob_controller.go | 30 +- .../ray/rayjob_controller_unit_test.go | 294 ++++++++++++++++++ 2 files changed, 322 insertions(+), 2 deletions(-) diff --git a/ray-operator/controllers/ray/rayjob_controller.go b/ray-operator/controllers/ray/rayjob_controller.go index b5492f93115..cac1bc176b3 100644 --- a/ray-operator/controllers/ray/rayjob_controller.go +++ b/ray-operator/controllers/ray/rayjob_controller.go @@ -579,8 +579,20 @@ func getSubmitterTemplate(rayJobInstance *rayv1.RayJob, rayClusterInstance *rayv } // getSubmitterContainer builds the submitter container for the Ray job Sidecar mode. +// If the user provides a SubmitterContainerTemplate, it is used as the base container. +// Otherwise, a default container is created using GetDefaultSubmitterContainer. func getSubmitterContainer(rayJobInstance *rayv1.RayJob, rayClusterInstance *rayv1.RayCluster) (corev1.Container, error) { - submitterContainer := common.GetDefaultSubmitterContainer(&rayClusterInstance.Spec) + if rayClusterInstance == nil { + return corev1.Container{}, fmt.Errorf("rayClusterInstance must not be nil for SidecarMode") + } + + var submitterContainer corev1.Container + + if rayJobInstance.Spec.SubmitterContainerTemplate != nil { + submitterContainer = *rayJobInstance.Spec.SubmitterContainerTemplate.DeepCopy() + } else { + submitterContainer = common.GetDefaultSubmitterContainer(&rayClusterInstance.Spec) + } if err := configureSubmitterContainer(&submitterContainer, rayJobInstance, rayClusterInstance, rayv1.SidecarMode); err != nil { return corev1.Container{}, err @@ -591,6 +603,20 @@ func getSubmitterContainer(rayJobInstance *rayv1.RayJob, rayClusterInstance *ray // pass the RayCluster instance for cluster selector case func configureSubmitterContainer(container *corev1.Container, rayJobInstance *rayv1.RayJob, rayClusterInstance *rayv1.RayCluster, submissionMode rayv1.JobSubmissionMode) error { + // Always set the container name to the expected submitter container name + container.Name = utils.SubmitterContainerName + + // If the image is not specified, use the image of the Ray head container + if container.Image == "" && rayClusterInstance != nil { + container.Image = rayClusterInstance.Spec.HeadGroupSpec.Template.Spec.Containers[utils.RayContainerIndex].Image + } + + // If resources are not specified, use default resources + if container.Resources.Limits == nil && container.Resources.Requests == nil && rayClusterInstance != nil { + defaultContainer := common.GetDefaultSubmitterContainer(&rayClusterInstance.Spec) + container.Resources = defaultContainer.Resources + } + // If the command in the submitter container manifest isn't set, use the default command. jobCmd, err := common.BuildJobSubmitCommand(rayJobInstance, submissionMode) if err != nil { @@ -598,7 +624,7 @@ func configureSubmitterContainer(container *corev1.Container, rayJobInstance *ra } // K8sJobMode: If the user doesn't specify the command, use the default command. - // SidecarMode: Use the default command. + // SidecarMode: Always use the default command (command is overwritten). if len(container.Command) == 0 || submissionMode == rayv1.SidecarMode { // Without the -e option, the Bash script will continue executing even if a command returns a non-zero exit code. container.Command = utils.GetContainerCommand([]string{"e"}) diff --git a/ray-operator/controllers/ray/rayjob_controller_unit_test.go b/ray-operator/controllers/ray/rayjob_controller_unit_test.go index eb37b1a729a..f473b5482dc 100644 --- a/ray-operator/controllers/ray/rayjob_controller_unit_test.go +++ b/ray-operator/controllers/ray/rayjob_controller_unit_test.go @@ -14,6 +14,7 @@ import ( "go.uber.org/mock/gomock" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -201,6 +202,299 @@ func TestGetSubmitterTemplate(t *testing.T) { assert.Equal(t, "test-job-id", envVar.Value) } +func TestGetSubmitterContainer(t *testing.T) { + // Helper to create a basic RayClusterSpec with a head container + basicRayClusterSpec := func() *rayv1.RayClusterSpec { + return &rayv1.RayClusterSpec{ + HeadGroupSpec: rayv1.HeadGroupSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Image: "rayproject/ray:custom-version", + }, + }, + }, + }, + }, + } + } + + rayClusterInstance := &rayv1.RayCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-raycluster", + Namespace: "default", + }, + Spec: *basicRayClusterSpec(), + } + + t.Run("Default container without SubmitterContainerTemplate", func(t *testing.T) { + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: nil, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // Should use default values + assert.Equal(t, utils.SubmitterContainerName, container.Name) + assert.Equal(t, "rayproject/ray:custom-version", container.Image) + assert.NotEmpty(t, container.Resources.Limits) + assert.NotEmpty(t, container.Resources.Requests) + }) + + t.Run("Custom container with SubmitterContainerTemplate", func(t *testing.T) { + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: &corev1.Container{ + Image: "custom/image:v1", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "scripts", + MountPath: "/scripts", + }, + }, + Env: []corev1.EnvVar{ + { + Name: "MY_VAR", + Value: "my_value", + }, + }, + }, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // Container name should be overwritten + assert.Equal(t, utils.SubmitterContainerName, container.Name) + // Custom image should be preserved + assert.Equal(t, "custom/image:v1", container.Image) + // Volume mounts should be preserved + assert.Len(t, container.VolumeMounts, 1) + assert.Equal(t, "scripts", container.VolumeMounts[0].Name) + assert.Equal(t, "/scripts", container.VolumeMounts[0].MountPath) + // User env vars should be present (before controller env vars) + envVar, found := utils.EnvVarByName("MY_VAR", container.Env) + assert.True(t, found) + assert.Equal(t, "my_value", envVar.Value) + // Controller env vars should also be present + envVar, found = utils.EnvVarByName(utils.RAY_DASHBOARD_ADDRESS, container.Env) + assert.True(t, found) + assert.Equal(t, "test-url", envVar.Value) + }) + + t.Run("Image inheritance when not specified in template", func(t *testing.T) { + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: &corev1.Container{ + // Image not specified - should inherit from head container + VolumeMounts: []corev1.VolumeMount{ + { + Name: "data", + MountPath: "/data", + }, + }, + }, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // Should inherit image from head container + assert.Equal(t, "rayproject/ray:custom-version", container.Image) + // Volume mounts should still be preserved + assert.Len(t, container.VolumeMounts, 1) + assert.Equal(t, "data", container.VolumeMounts[0].Name) + }) + + t.Run("Container name is always overwritten", func(t *testing.T) { + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: &corev1.Container{ + Name: "user-specified-name", // Should be overwritten + Image: "custom/image:v1", + }, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // Name should be overwritten to the standard submitter container name + assert.Equal(t, utils.SubmitterContainerName, container.Name) + }) + + t.Run("Command and Args are always overwritten in SidecarMode", func(t *testing.T) { + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: &corev1.Container{ + Image: "custom/image:v1", + Command: []string{"user-command"}, // Should be overwritten + Args: []string{"user-args"}, // Should be overwritten + }, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // Command should be overwritten to the controller's command + assert.Equal(t, []string{"/bin/bash", "-ce", "--"}, container.Command) + // Args should contain the job submission command + assert.Len(t, container.Args, 1) + assert.Contains(t, container.Args[0], "ray job submit") + }) + + t.Run("Default resources when not specified in template", func(t *testing.T) { + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: &corev1.Container{ + Image: "custom/image:v1", + // Resources not specified - should use defaults + }, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // Should have default resources + assert.NotEmpty(t, container.Resources.Limits) + assert.NotEmpty(t, container.Resources.Requests) + }) + + t.Run("Custom resources are preserved", func(t *testing.T) { + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: &corev1.Container{ + Image: "custom/image:v1", + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + corev1.ResourceMemory: resource.MustParse("2Gi"), + }, + }, + }, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // Custom resources should be preserved + assert.NotEmpty(t, container.Resources.Limits) + assert.Equal(t, resource.MustParse("2"), container.Resources.Limits[corev1.ResourceCPU]) + }) + + t.Run("Security context is preserved", func(t *testing.T) { + runAsUser := int64(1000) + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: &corev1.Container{ + Image: "custom/image:v1", + SecurityContext: &corev1.SecurityContext{ + RunAsUser: &runAsUser, + RunAsNonRoot: pointer.Bool(true), + }, + }, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // Security context should be preserved + assert.NotNil(t, container.SecurityContext) + assert.Equal(t, int64(1000), *container.SecurityContext.RunAsUser) + assert.True(t, *container.SecurityContext.RunAsNonRoot) + }) + + t.Run("EnvFrom is preserved", func(t *testing.T) { + rayJobInstance := &rayv1.RayJob{ + Spec: rayv1.RayJobSpec{ + Entrypoint: "python script.py", + SubmitterContainerTemplate: &corev1.Container{ + Image: "custom/image:v1", + EnvFrom: []corev1.EnvFromSource{ + { + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-config", + }, + }, + }, + }, + }, + RayClusterSpec: basicRayClusterSpec(), + }, + Status: rayv1.RayJobStatus{ + DashboardURL: "test-url", + JobId: "test-job-id", + }, + } + + container, err := getSubmitterContainer(rayJobInstance, rayClusterInstance) + require.NoError(t, err) + + // EnvFrom should be preserved + assert.Len(t, container.EnvFrom, 1) + assert.Equal(t, "my-config", container.EnvFrom[0].ConfigMapRef.Name) + }) +} + func TestUpdateStatusToSuspendingIfNeeded(t *testing.T) { newScheme := runtime.NewScheme() _ = rayv1.AddToScheme(newScheme) From f94675eafe5430a8afe5576cf47d4419f5c366aa Mon Sep 17 00:00:00 2001 From: Mark Rossett Date: Mon, 2 Feb 2026 11:28:01 -0800 Subject: [PATCH 4/5] Added E2e test Signed-off-by: Mark Rossett --- .../e2erayjob/rayjob_sidecar_mode_test.go | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/ray-operator/test/e2erayjob/rayjob_sidecar_mode_test.go b/ray-operator/test/e2erayjob/rayjob_sidecar_mode_test.go index 62f6a3c7d33..d3da86b429a 100644 --- a/ray-operator/test/e2erayjob/rayjob_sidecar_mode_test.go +++ b/ray-operator/test/e2erayjob/rayjob_sidecar_mode_test.go @@ -290,4 +290,107 @@ env_vars: LogWithTimestamp(test.T(), "RayJob %s/%s completed successfully with auth token", rayJob.Namespace, rayJob.Name) }) + + test.T().Run("SidecarMode with SubmitterContainerTemplate", func(_ *testing.T) { + // This test verifies that SubmitterContainerTemplate allows users to customize + // the sidecar submitter container with volume mounts and environment variables. + rayJobAC := rayv1ac.RayJob("sidecar-custom-submitter", namespace.Name). + WithSpec(rayv1ac.RayJobSpec(). + WithSubmissionMode(rayv1.SidecarMode). + WithEntrypoint("python /home/ray/jobs/counter.py"). + WithRuntimeEnvYAML(` +env_vars: + counter_name: test_counter +`). + WithShutdownAfterJobFinishes(true). + // Use SubmitterContainerTemplate to customize the submitter container + WithSubmitterContainerTemplate(corev1.Container{ + // Volume mounts for the submitter container + VolumeMounts: []corev1.VolumeMount{ + { + Name: jobs.Name, + MountPath: "/home/ray/jobs", + }, + }, + // Custom environment variable + Env: []corev1.EnvVar{ + { + Name: "CUSTOM_SUBMITTER_VAR", + Value: "submitter_template_test", + }, + }, + }). + // Match the working "Successful RayJob" test pattern + WithRayClusterSpec(rayv1ac.RayClusterSpec(). + WithRayVersion(GetRayVersion()). + WithHeadGroupSpec(rayv1ac.HeadGroupSpec(). + WithRayStartParams(map[string]string{"dashboard-host": "0.0.0.0"}). + WithTemplate(PodTemplateSpecApplyConfiguration(HeadPodTemplateApplyConfiguration(), + MountConfigMap[corev1ac.PodTemplateSpecApplyConfiguration](jobs, "/home/ray/jobs")))))) + + rayJob, err := test.Client().Ray().RayV1().RayJobs(namespace.Name).Apply(test.Ctx(), rayJobAC, TestApplyOptions) + g.Expect(err).NotTo(HaveOccurred()) + LogWithTimestamp(test.T(), "Created RayJob %s/%s with SubmitterContainerTemplate", rayJob.Namespace, rayJob.Name) + + // Wait for RayCluster to be created and ready + LogWithTimestamp(test.T(), "Waiting for RayCluster to be created") + g.Eventually(RayJob(test, rayJob.Namespace, rayJob.Name), TestTimeoutMedium). + Should(WithTransform(RayJobClusterName, Not(BeEmpty()))) + + rayJob, err = GetRayJob(test, rayJob.Namespace, rayJob.Name) + g.Expect(err).NotTo(HaveOccurred()) + + // Verify the submitter container has the expected configuration + rayCluster, err := GetRayCluster(test, namespace.Name, rayJob.Status.RayClusterName) + g.Expect(err).NotTo(HaveOccurred()) + + headPod, err := GetHeadPod(test, rayCluster) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(headPod).NotTo(BeNil()) + + // Find the submitter container + var submitterContainer *corev1.Container + for i := range headPod.Spec.Containers { + if headPod.Spec.Containers[i].Name == utils.SubmitterContainerName { + submitterContainer = &headPod.Spec.Containers[i] + break + } + } + g.Expect(submitterContainer).NotTo(BeNil(), "submitter container should be present in head pod") + + // Verify the volume mount is present + hasVolumeMount := false + for _, vm := range submitterContainer.VolumeMounts { + if vm.Name == jobs.Name && vm.MountPath == "/home/ray/jobs" { + hasVolumeMount = true + break + } + } + g.Expect(hasVolumeMount).To(BeTrue(), "submitter container should have the ConfigMap volume mount") + + // Verify the custom environment variable is present + hasCustomEnv := false + for _, env := range submitterContainer.Env { + if env.Name == "CUSTOM_SUBMITTER_VAR" && env.Value == "submitter_template_test" { + hasCustomEnv = true + break + } + } + g.Expect(hasCustomEnv).To(BeTrue(), "submitter container should have the custom environment variable") + + // Wait for the job to complete + LogWithTimestamp(test.T(), "Waiting for RayJob %s/%s to complete", rayJob.Namespace, rayJob.Name) + g.Eventually(RayJob(test, rayJob.Namespace, rayJob.Name), TestTimeoutMedium). + Should(WithTransform(RayJobStatus, Satisfy(rayv1.IsJobTerminal))) + + // Assert the RayJob has completed successfully + g.Expect(GetRayJob(test, rayJob.Namespace, rayJob.Name)). + To(WithTransform(RayJobStatus, Equal(rayv1.JobStatusSucceeded))) + + // And the RayJob deployment status is updated accordingly + g.Eventually(RayJob(test, rayJob.Namespace, rayJob.Name)). + Should(WithTransform(RayJobDeploymentStatus, Equal(rayv1.JobDeploymentStatusComplete))) + + LogWithTimestamp(test.T(), "RayJob %s/%s with SubmitterContainerTemplate completed successfully", rayJob.Namespace, rayJob.Name) + }) } From b8b5883e794e50830bcdddedb0304054dc0af99b Mon Sep 17 00:00:00 2001 From: Mark Rossett Date: Tue, 3 Feb 2026 13:50:52 -0800 Subject: [PATCH 5/5] Update helm chart CRDs Signed-off-by: Mark Rossett --- .../crds/ray.io_raycronjobs.yaml | 720 ++++++++++++++++++ .../kuberay-operator/crds/ray.io_rayjobs.yaml | 720 ++++++++++++++++++ 2 files changed, 1440 insertions(+) diff --git a/helm-chart/kuberay-operator/crds/ray.io_raycronjobs.yaml b/helm-chart/kuberay-operator/crds/ray.io_raycronjobs.yaml index c2a3f252a89..030624136da 100644 --- a/helm-chart/kuberay-operator/crds/ray.io_raycronjobs.yaml +++ b/helm-chart/kuberay-operator/crds/ray.io_raycronjobs.yaml @@ -8536,6 +8536,726 @@ spec: format: int32 type: integer type: object + submitterContainerTemplate: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + type: string + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object submitterPodTemplate: properties: metadata: diff --git a/helm-chart/kuberay-operator/crds/ray.io_rayjobs.yaml b/helm-chart/kuberay-operator/crds/ray.io_rayjobs.yaml index f4e0260e101..371942a527f 100644 --- a/helm-chart/kuberay-operator/crds/ray.io_rayjobs.yaml +++ b/helm-chart/kuberay-operator/crds/ray.io_rayjobs.yaml @@ -8540,6 +8540,726 @@ spec: format: int32 type: integer type: object + submitterContainerTemplate: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + type: string + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object submitterPodTemplate: properties: metadata: