Skip to content

Commit b85cf85

Browse files
authored
Allow custom ports for livenessProbe when multi-container-probing is enabled (#16572)
When the multi-container-probing feature flag is enabled, the reconciler now preserves user-specified custom ports on the primary container's HTTP and TCP liveness probes instead of overwriting them to the serving containerPort. This supports applications (e.g. Spring Boot Actuator) that expose health endpoints on a separate management port. When the flag is disabled (default), existing strict behavior is preserved. Signed-off-by: Mikhail Fedosin <mfedosin@redhat.com>
1 parent 7c94473 commit b85cf85

3 files changed

Lines changed: 170 additions & 9 deletions

File tree

pkg/reconciler/revision/resources/deploy.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,19 @@ func certVolume(secret string) corev1.Volume {
138138
}
139139
}
140140

141-
func rewriteUserLivenessProbe(p *corev1.Probe, userPort int) {
141+
func rewriteUserLivenessProbe(p *corev1.Probe, userPort int, allowCustomPort bool) {
142142
if p == nil {
143143
return
144144
}
145145
switch {
146146
case p.HTTPGet != nil:
147-
p.HTTPGet.Port = intstr.FromInt(userPort)
147+
if !allowCustomPort || p.HTTPGet.Port.IntValue() == 0 {
148+
p.HTTPGet.Port = intstr.FromInt(userPort)
149+
}
148150
case p.TCPSocket != nil:
149-
p.TCPSocket.Port = intstr.FromInt(userPort)
151+
if !allowCustomPort || p.TCPSocket.Port.IntValue() == 0 {
152+
p.TCPSocket.Port = intstr.FromInt(userPort)
153+
}
150154
}
151155
}
152156

@@ -206,7 +210,7 @@ func makePodSpec(rev *v1.Revision, cfg *config.Config) (*corev1.PodSpec, error)
206210
extraVolumes = append(extraVolumes, certVolume(networking.ServingCertName))
207211
}
208212

209-
podSpec := BuildPodSpec(rev, append(BuildUserContainers(rev), *queueContainer), cfg)
213+
podSpec := BuildPodSpec(rev, append(BuildUserContainers(rev, cfg.Features), *queueContainer), cfg)
210214
podSpec.Volumes = append(podSpec.Volumes, extraVolumes...)
211215

212216
if val := cfg.Deployment.PodRuntimeClassName(rev.ObjectMeta.Labels); podSpec.RuntimeClassName == nil {
@@ -237,12 +241,14 @@ func makePodSpec(rev *v1.Revision, cfg *config.Config) (*corev1.PodSpec, error)
237241
}
238242

239243
// BuildUserContainers makes an array of containers from the Revision template.
240-
func BuildUserContainers(rev *v1.Revision) []corev1.Container {
244+
// features may be nil, in which case default behavior (strict port rewriting) is used.
245+
func BuildUserContainers(rev *v1.Revision, features *apiconfig.Features) []corev1.Container {
246+
allowCustomPort := features != nil && features.MultiContainerProbing == apiconfig.Enabled
241247
containers := make([]corev1.Container, 0, len(rev.Spec.PodSpec.Containers))
242248
for i := range rev.Spec.PodSpec.Containers {
243249
var container corev1.Container
244250
if len(rev.Spec.PodSpec.Containers[i].Ports) != 0 || len(rev.Spec.PodSpec.Containers) == 1 {
245-
container = makeServingContainer(*rev.Spec.PodSpec.Containers[i].DeepCopy(), rev)
251+
container = makeServingContainer(*rev.Spec.PodSpec.Containers[i].DeepCopy(), rev, allowCustomPort)
246252
} else {
247253
container = makeContainer(*rev.Spec.PodSpec.Containers[i].DeepCopy(), rev)
248254
}
@@ -283,15 +289,15 @@ func makeContainer(container corev1.Container, rev *v1.Revision) corev1.Containe
283289
return container
284290
}
285291

286-
func makeServingContainer(servingContainer corev1.Container, rev *v1.Revision) corev1.Container {
292+
func makeServingContainer(servingContainer corev1.Container, rev *v1.Revision, allowCustomPort bool) corev1.Container {
287293
userPort := getUserPort(rev)
288294
userPortStr := strconv.Itoa(int(userPort))
289295
// Replacement is safe as only up to a single port is allowed on the Revision
290296
servingContainer.Ports = buildContainerPorts(userPort)
291297
servingContainer.Env = append(servingContainer.Env, buildUserPortEnv(userPortStr))
292298
container := makeContainer(servingContainer, rev)
293299
// If the user provides a liveness probe, we should rewrite in the port on the user-container for them.
294-
rewriteUserLivenessProbe(container.LivenessProbe, int(userPort))
300+
rewriteUserLivenessProbe(container.LivenessProbe, int(userPort), allowCustomPort)
295301
return container
296302
}
297303

pkg/reconciler/revision/resources/deploy_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,161 @@ func TestMakePodSpec(t *testing.T) {
10771077
),
10781078
queueContainer(),
10791079
}),
1080+
}, {
1081+
name: "with HTTP liveness probe on custom port and multi-container-probing enabled",
1082+
fc: apicfg.Features{
1083+
MultiContainerProbing: apicfg.Enabled,
1084+
},
1085+
rev: revision("bar", "foo",
1086+
withContainers([]corev1.Container{{
1087+
Name: servingContainerName,
1088+
Image: "busybox",
1089+
ReadinessProbe: withTCPReadinessProbe(v1.DefaultUserPort),
1090+
LivenessProbe: &corev1.Probe{
1091+
ProbeHandler: corev1.ProbeHandler{
1092+
HTTPGet: &corev1.HTTPGetAction{
1093+
Path: "/health",
1094+
Port: intstr.FromInt32(8081),
1095+
},
1096+
},
1097+
},
1098+
}}),
1099+
WithContainerStatuses([]v1.ContainerStatus{{
1100+
ImageDigest: "busybox@sha256:deadbeef",
1101+
}}),
1102+
),
1103+
want: podSpec(
1104+
[]corev1.Container{
1105+
servingContainer(
1106+
func(container *corev1.Container) {
1107+
container.Image = "busybox@sha256:deadbeef"
1108+
},
1109+
withLivenessProbe(corev1.ProbeHandler{
1110+
HTTPGet: &corev1.HTTPGetAction{
1111+
Path: "/health",
1112+
Port: intstr.FromInt32(8081),
1113+
},
1114+
}),
1115+
),
1116+
queueContainer(
1117+
withEnvVar("ENABLE_MULTI_CONTAINER_PROBES", "true"),
1118+
withEnvVar("SERVING_READINESS_PROBE", `[{"tcpSocket":{"port":8080,"host":"127.0.0.1"}}]`),
1119+
),
1120+
}),
1121+
}, {
1122+
name: "with TCP liveness probe on custom port and multi-container-probing enabled",
1123+
fc: apicfg.Features{
1124+
MultiContainerProbing: apicfg.Enabled,
1125+
},
1126+
rev: revision("bar", "foo",
1127+
withContainers([]corev1.Container{{
1128+
Name: servingContainerName,
1129+
Image: "busybox",
1130+
ReadinessProbe: withTCPReadinessProbe(v1.DefaultUserPort),
1131+
LivenessProbe: &corev1.Probe{
1132+
ProbeHandler: corev1.ProbeHandler{
1133+
TCPSocket: &corev1.TCPSocketAction{
1134+
Port: intstr.FromInt32(8081),
1135+
},
1136+
},
1137+
},
1138+
}}),
1139+
WithContainerStatuses([]v1.ContainerStatus{{
1140+
ImageDigest: "busybox@sha256:deadbeef",
1141+
}}),
1142+
),
1143+
want: podSpec(
1144+
[]corev1.Container{
1145+
servingContainer(
1146+
func(container *corev1.Container) {
1147+
container.Image = "busybox@sha256:deadbeef"
1148+
},
1149+
withLivenessProbe(corev1.ProbeHandler{
1150+
TCPSocket: &corev1.TCPSocketAction{
1151+
Port: intstr.FromInt32(8081),
1152+
},
1153+
}),
1154+
),
1155+
queueContainer(
1156+
withEnvVar("ENABLE_MULTI_CONTAINER_PROBES", "true"),
1157+
withEnvVar("SERVING_READINESS_PROBE", `[{"tcpSocket":{"port":8080,"host":"127.0.0.1"}}]`),
1158+
),
1159+
}),
1160+
}, {
1161+
name: "with HTTP liveness probe without port and multi-container-probing enabled",
1162+
fc: apicfg.Features{
1163+
MultiContainerProbing: apicfg.Enabled,
1164+
},
1165+
rev: revision("bar", "foo",
1166+
withContainers([]corev1.Container{{
1167+
Name: servingContainerName,
1168+
Image: "busybox",
1169+
ReadinessProbe: withTCPReadinessProbe(v1.DefaultUserPort),
1170+
LivenessProbe: &corev1.Probe{
1171+
ProbeHandler: corev1.ProbeHandler{
1172+
HTTPGet: &corev1.HTTPGetAction{
1173+
Path: "/",
1174+
},
1175+
},
1176+
},
1177+
}}),
1178+
WithContainerStatuses([]v1.ContainerStatus{{
1179+
ImageDigest: "busybox@sha256:deadbeef",
1180+
}}),
1181+
),
1182+
want: podSpec(
1183+
[]corev1.Container{
1184+
servingContainer(
1185+
func(container *corev1.Container) {
1186+
container.Image = "busybox@sha256:deadbeef"
1187+
},
1188+
withLivenessProbe(corev1.ProbeHandler{
1189+
HTTPGet: &corev1.HTTPGetAction{
1190+
Path: "/",
1191+
Port: intstr.FromInt32(v1.DefaultUserPort),
1192+
},
1193+
}),
1194+
),
1195+
queueContainer(
1196+
withEnvVar("ENABLE_MULTI_CONTAINER_PROBES", "true"),
1197+
withEnvVar("SERVING_READINESS_PROBE", `[{"tcpSocket":{"port":8080,"host":"127.0.0.1"}}]`),
1198+
),
1199+
}),
1200+
}, {
1201+
name: "with HTTP liveness probe on custom port and multi-container-probing disabled",
1202+
rev: revision("bar", "foo",
1203+
withContainers([]corev1.Container{{
1204+
Name: servingContainerName,
1205+
Image: "busybox",
1206+
ReadinessProbe: withTCPReadinessProbe(v1.DefaultUserPort),
1207+
LivenessProbe: &corev1.Probe{
1208+
ProbeHandler: corev1.ProbeHandler{
1209+
HTTPGet: &corev1.HTTPGetAction{
1210+
Path: "/health",
1211+
Port: intstr.FromInt32(8081),
1212+
},
1213+
},
1214+
},
1215+
}}),
1216+
WithContainerStatuses([]v1.ContainerStatus{{
1217+
ImageDigest: "busybox@sha256:deadbeef",
1218+
}}),
1219+
),
1220+
want: podSpec(
1221+
[]corev1.Container{
1222+
servingContainer(
1223+
func(container *corev1.Container) {
1224+
container.Image = "busybox@sha256:deadbeef"
1225+
},
1226+
withLivenessProbe(corev1.ProbeHandler{
1227+
HTTPGet: &corev1.HTTPGetAction{
1228+
Path: "/health",
1229+
Port: intstr.FromInt32(v1.DefaultUserPort),
1230+
},
1231+
}),
1232+
),
1233+
queueContainer(),
1234+
}),
10801235
}, {
10811236
name: "with HTTP startup probe",
10821237
rev: revision("bar", "foo",

pkg/webhook/podspec_dryrun.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func validatePodSpec(ctx context.Context, ps v1.RevisionSpec, namespace string)
5959
Spec: ps,
6060
}
6161
rev.SetDefaults(ctx)
62-
podSpec := resources.BuildPodSpec(rev, resources.BuildUserContainers(rev), nil /*configs*/)
62+
podSpec := resources.BuildPodSpec(rev, resources.BuildUserContainers(rev, nil /*features*/), nil /*configs*/)
6363

6464
// Make a sample pod with the template Revisions & PodSpec and dryrun call to API-server
6565
pod := &corev1.Pod{

0 commit comments

Comments
 (0)