From 983235b2519a086e28d34a9457c0cea312d73749 Mon Sep 17 00:00:00 2001 From: Rahadyan Pramudita Date: Wed, 18 Feb 2026 13:20:18 +0700 Subject: [PATCH] introduce startup probe --- api/cluster/resource/templater.go | 65 +++++++++++++++++++ api/models/resource_request.go | 2 + swagger.yaml | 2 + ui/src/components/ResourcesConfigTable.js | 18 +++++ .../forms/DeployModelVersionForm.js | 6 ++ .../forms/components/ProbesFormGroup.js | 6 ++ ui/src/services/transformer/Transformer.js | 5 +- .../version_endpoint/VersionEndpoint.js | 1 + 8 files changed, 104 insertions(+), 1 deletion(-) diff --git a/api/cluster/resource/templater.go b/api/cluster/resource/templater.go index 0fab3d8f6..83e86c847 100644 --- a/api/cluster/resource/templater.go +++ b/api/cluster/resource/templater.go @@ -240,13 +240,16 @@ func (t *InferenceServiceTemplater) createPredictorSpec(modelService *models.Ser // Get user-configured probe settings var userLivenessConfig *models.ProbeConfig var userReadinessConfig *models.ProbeConfig + var userStartupConfig *models.ProbeConfig if modelService.ResourceRequest != nil { userLivenessConfig = modelService.ResourceRequest.LivenessProbe userReadinessConfig = modelService.ResourceRequest.ReadinessProbe + userStartupConfig = modelService.ResourceRequest.StartupProbe } livenessProbeConfig := getLivenessProbeConfig(modelService.PredictorProtocol(), envVars, fmt.Sprintf("/v1/models/%s", modelService.Name), userLivenessConfig) readinessProbeConfig := getReadinessProbeConfig(modelService.PredictorProtocol(), fmt.Sprintf("/v1/models/%s", modelService.Name), userReadinessConfig) + startupProbeConfig := getStartupProbeConfig(modelService.PredictorProtocol(), fmt.Sprintf("/v1/models/%s", modelService.Name), userStartupConfig) containerPorts := createContainerPorts(modelService.PredictorProtocol(), modelService.DeploymentMode) storageUri := utils.CreateModelLocation(modelService.ArtifactURI) @@ -262,6 +265,7 @@ func (t *InferenceServiceTemplater) createPredictorSpec(modelService *models.Ser Resources: resources, LivenessProbe: livenessProbeConfig, ReadinessProbe: readinessProbeConfig, + StartupProbe: startupProbeConfig, Ports: containerPorts, Env: envVars, }, @@ -278,6 +282,7 @@ func (t *InferenceServiceTemplater) createPredictorSpec(modelService *models.Ser Resources: resources, LivenessProbe: livenessProbeConfig, ReadinessProbe: readinessProbeConfig, + StartupProbe: startupProbeConfig, Ports: containerPorts, Env: envVars, }, @@ -294,6 +299,7 @@ func (t *InferenceServiceTemplater) createPredictorSpec(modelService *models.Ser Resources: resources, LivenessProbe: livenessProbeConfig, ReadinessProbe: readinessProbeConfig, + StartupProbe: startupProbeConfig, Ports: containerPorts, Env: envVars, }, @@ -310,6 +316,7 @@ func (t *InferenceServiceTemplater) createPredictorSpec(modelService *models.Ser Resources: resources, LivenessProbe: livenessProbeConfig, ReadinessProbe: readinessProbeConfig, + StartupProbe: startupProbeConfig, Ports: containerPorts, Env: envVars, }, @@ -354,6 +361,7 @@ func (t *InferenceServiceTemplater) createPredictorSpec(modelService *models.Ser Resources: resources, LivenessProbe: livenessProbeConfig, ReadinessProbe: readinessProbeConfig, + StartupProbe: startupProbeConfig, Ports: containerPorts, }, }, @@ -428,13 +436,16 @@ func (t *InferenceServiceTemplater) createTransformerSpec( // Get user-configured probe settings for transformer var userLivenessConfig *models.ProbeConfig var userReadinessConfig *models.ProbeConfig + var userStartupConfig *models.ProbeConfig if transformer.ResourceRequest != nil { userLivenessConfig = transformer.ResourceRequest.LivenessProbe userReadinessConfig = transformer.ResourceRequest.ReadinessProbe + userStartupConfig = transformer.ResourceRequest.StartupProbe } livenessProbeConfig := getLivenessProbeConfig(modelService.Protocol, envVars, "/", userLivenessConfig) readinessProbeConfig := getReadinessProbeConfig(modelService.Protocol, "/", userReadinessConfig) + startupProbeConfig := getStartupProbeConfig(modelService.Protocol, "/", userStartupConfig) containerPorts := createContainerPorts(modelService.Protocol, modelService.DeploymentMode) transformerSpec := &kservev1beta1.TransformerSpec{ @@ -455,6 +466,7 @@ func (t *InferenceServiceTemplater) createTransformerSpec( Args: transformerArgs, LivenessProbe: livenessProbeConfig, ReadinessProbe: readinessProbeConfig, + StartupProbe: startupProbeConfig, Ports: containerPorts, }, }, @@ -648,6 +660,59 @@ func createGRPCReadinessProbe(port int, userConfig *models.ProbeConfig) *corev1. return probe } +// getStartupProbeConfig creates a startup probe configuration based on user settings +func getStartupProbeConfig(protocol prt.Protocol, httpPath string, userConfig *models.ProbeConfig) *corev1.Probe { + if userConfig == nil { + return nil + } + return createStartupProbeSpec(protocol, httpPath, userConfig) +} + +func createStartupProbeSpec(protocol prt.Protocol, httpPath string, userConfig *models.ProbeConfig) *corev1.Probe { + if protocol == prt.UpiV1 { + return createGRPCStartupProbe(defaultGRPCPort, userConfig) + } + return createHTTPGetStartupProbe(httpPath, defaultHTTPPort, userConfig) +} + +func createHTTPGetStartupProbe(httpPath string, port int, userConfig *models.ProbeConfig) *corev1.Probe { + probe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: httpPath, + Scheme: "HTTP", + Port: intstr.IntOrString{ + IntVal: int32(port), + }, + }, + }, + InitialDelaySeconds: liveProbeInitialDelaySec, + TimeoutSeconds: liveProbeTimeoutSec, + PeriodSeconds: liveProbePeriodSec, + SuccessThreshold: liveProbeSuccessThreshold, + FailureThreshold: liveProbeFailureThreshold, + } + applyUserProbeConfig(probe, userConfig, port) + return probe +} + +func createGRPCStartupProbe(port int, userConfig *models.ProbeConfig) *corev1.Probe { + probe := &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{grpcHealthProbeCommand, fmt.Sprintf("-addr=:%d", port)}, + }, + }, + InitialDelaySeconds: liveProbeInitialDelaySec, + TimeoutSeconds: liveProbeTimeoutSec, + PeriodSeconds: liveProbePeriodSec, + SuccessThreshold: liveProbeSuccessThreshold, + FailureThreshold: liveProbeFailureThreshold, + } + applyUserProbeConfig(probe, userConfig, port) + return probe +} + // applyUserProbeConfig applies user-provided probe configuration to the probe func applyUserProbeConfig(probe *corev1.Probe, userConfig *models.ProbeConfig, _ int) { if userConfig == nil { diff --git a/api/models/resource_request.go b/api/models/resource_request.go index d335c9632..0a6f02ba2 100644 --- a/api/models/resource_request.go +++ b/api/models/resource_request.go @@ -41,6 +41,8 @@ type ResourceRequest struct { LivenessProbe *ProbeConfig `json:"liveness_probe,omitempty"` // Readiness probe configuration ReadinessProbe *ProbeConfig `json:"readiness_probe,omitempty"` + // Startup probe configuration + StartupProbe *ProbeConfig `json:"startup_probe,omitempty"` } // ProbeConfig represents the configuration for Kubernetes liveness/readiness probes diff --git a/swagger.yaml b/swagger.yaml index 65bece648..7ebc7e522 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -2121,6 +2121,8 @@ components: "$ref": "#/components/schemas/ProbeConfig" readiness_probe: "$ref": "#/components/schemas/ProbeConfig" + startup_probe: + "$ref": "#/components/schemas/ProbeConfig" ProbeConfig: type: object properties: diff --git a/ui/src/components/ResourcesConfigTable.js b/ui/src/components/ResourcesConfigTable.js index dcf9a95cd..b70c5416f 100644 --- a/ui/src/components/ResourcesConfigTable.js +++ b/ui/src/components/ResourcesConfigTable.js @@ -29,6 +29,7 @@ export const ResourcesConfigTable = ({ gpu_request, liveness_probe, readiness_probe, + startup_probe, }, }) => { const items = [ @@ -104,6 +105,23 @@ export const ResourcesConfigTable = ({ } } + // Add startup probe info if configured + if (startup_probe && Object.keys(startup_probe).some(k => startup_probe[k])) { + const probeDetails = []; + if (startup_probe.initial_delay_seconds) probeDetails.push(`delay: ${startup_probe.initial_delay_seconds}s`); + if (startup_probe.timeout_seconds) probeDetails.push(`timeout: ${startup_probe.timeout_seconds}s`); + if (startup_probe.period_seconds) probeDetails.push(`period: ${startup_probe.period_seconds}s`); + if (startup_probe.failure_threshold) probeDetails.push(`failures: ${startup_probe.failure_threshold}`); + if (startup_probe.success_threshold) probeDetails.push(`successes: ${startup_probe.success_threshold}`); + if (startup_probe.path) probeDetails.push(`path: ${startup_probe.path}`); + if (probeDetails.length > 0) { + items.push({ + title: "Startup Probe", + description: probeDetails.join(", "), + }); + } + } + return ( probe[k])) { + delete versionEndpoint.resource_request.startup_probe; + } + } if (versionEndpoint?.image_builder_resource_request?.cpu_request === "") { delete versionEndpoint.image_builder_resource_request.cpu_request; } diff --git a/ui/src/pages/version/components/forms/components/ProbesFormGroup.js b/ui/src/pages/version/components/forms/components/ProbesFormGroup.js index 3db3fa96a..83aeffc5c 100644 --- a/ui/src/pages/version/components/forms/components/ProbesFormGroup.js +++ b/ui/src/pages/version/components/forms/components/ProbesFormGroup.js @@ -195,6 +195,12 @@ export const ProbesFormGroup = ({ "Readiness Probe", "Configure the readiness probe to determine if the container is ready to receive traffic. Empty values use platform defaults." )} + + {renderProbeFields( + "startup_probe", + "Startup Probe", + "Configure the startup probe to determine when the container has started. Useful for slow-starting containers. Empty values use platform defaults." + )} ); }; diff --git a/ui/src/services/transformer/Transformer.js b/ui/src/services/transformer/Transformer.js index 01bd4fb5b..943dc223c 100644 --- a/ui/src/services/transformer/Transformer.js +++ b/ui/src/services/transformer/Transformer.js @@ -23,7 +23,10 @@ export class Transformer { max_replica: process.env.REACT_APP_ENVIRONMENT === "production" ? 4 : 2, cpu_request: "500m", cpu_limit: "", - memory_request: "512Mi" + memory_request: "512Mi", + liveness_probe: null, + readiness_probe: null, + startup_probe: null, }; this.env_vars = []; diff --git a/ui/src/services/version_endpoint/VersionEndpoint.js b/ui/src/services/version_endpoint/VersionEndpoint.js index 465058ddc..589a81edb 100644 --- a/ui/src/services/version_endpoint/VersionEndpoint.js +++ b/ui/src/services/version_endpoint/VersionEndpoint.js @@ -30,6 +30,7 @@ export class VersionEndpoint { memory_request: "512Mi", liveness_probe: null, readiness_probe: null, + startup_probe: null, }; this.image_builder_resource_request = {