-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add multi-container probing #14853
Add multi-container probing #14853
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,7 @@ func TestFeaturesConfiguration(t *testing.T) { | |
wantErr: false, | ||
wantFeatures: defaultWith(&Features{ | ||
MultiContainer: Enabled, | ||
MultiContainerProbing: Enabled, | ||
PodSpecAffinity: Enabled, | ||
PodSpecTopologySpreadConstraints: Enabled, | ||
PodSpecDryRun: Enabled, | ||
|
@@ -79,6 +80,7 @@ func TestFeaturesConfiguration(t *testing.T) { | |
}), | ||
data: map[string]string{ | ||
"multi-container": "Enabled", | ||
"multi-container-probing": "Enabled", | ||
"kubernetes.podspec-affinity": "Enabled", | ||
"kubernetes.podspec-topologyspreadconstraints": "Enabled", | ||
"kubernetes.podspec-dryrun": "Enabled", | ||
|
@@ -114,6 +116,24 @@ func TestFeaturesConfiguration(t *testing.T) { | |
data: map[string]string{ | ||
"multi-container": "Disabled", | ||
}, | ||
}, { | ||
name: "multi-container-probing Allowed", | ||
wantErr: false, | ||
wantFeatures: defaultWith(&Features{ | ||
MultiContainerProbing: Allowed, | ||
}), | ||
data: map[string]string{ | ||
"multi-container-probing": "Allowed", | ||
}, | ||
}, { | ||
name: "multi-container-probing Disabled", | ||
wantErr: false, | ||
wantFeatures: defaultWith(&Features{ | ||
MultiContainerProbing: Disabled, | ||
}), | ||
data: map[string]string{ | ||
"multi-container-probing": "Disabled", | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistency reasons I think you need another test with name: "multi-container-probing Enabled". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we have it here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typically what we have for other features is this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I copied the "multi-container" one. It has only two cases. I think because it already tests Enabled in the |
||
}, { | ||
name: "kubernetes.podspec-affinity Allowed", | ||
wantErr: false, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -387,7 +387,7 @@ func ValidatePodSpec(ctx context.Context, ps corev1.PodSpec) *apis.FieldError { | |
case 0: | ||
errs = errs.Also(apis.ErrMissingField("containers")) | ||
case 1: | ||
errs = errs.Also(ValidateContainer(ctx, ps.Containers[0], volumes, port). | ||
errs = errs.Also(ValidateUserContainer(ctx, ps.Containers[0], volumes, port). | ||
ViaFieldIndex("containers", 0)) | ||
default: | ||
errs = errs.Also(validateContainers(ctx, ps.Containers, volumes, port)) | ||
|
@@ -447,7 +447,7 @@ func validateContainers(ctx context.Context, containers []corev1.Container, volu | |
// Note, if we allow readiness/liveness checks on sidecars, we should pass in an *empty* port here, not the main container's port. | ||
errs = errs.Also(validateSidecarContainer(WithinSidecarContainer(ctx), containers[i], volumes).ViaFieldIndex("containers", i)) | ||
} else { | ||
errs = errs.Also(ValidateContainer(WithinUserContainer(ctx), containers[i], volumes, port).ViaFieldIndex("containers", i)) | ||
errs = errs.Also(ValidateUserContainer(WithinUserContainer(ctx), containers[i], volumes, port).ViaFieldIndex("containers", i)) | ||
} | ||
} | ||
return errs | ||
|
@@ -503,14 +503,23 @@ func validateContainersPorts(containers []corev1.Container) (corev1.ContainerPor | |
|
||
// validateSidecarContainer validate fields for non serving containers | ||
func validateSidecarContainer(ctx context.Context, container corev1.Container, volumes map[string]corev1.Volume) (errs *apis.FieldError) { | ||
if container.LivenessProbe != nil { | ||
errs = errs.Also(apis.CheckDisallowedFields(*container.LivenessProbe, | ||
*ProbeMask(&corev1.Probe{})).ViaField("livenessProbe")) | ||
} | ||
if container.ReadinessProbe != nil { | ||
errs = errs.Also(apis.CheckDisallowedFields(*container.ReadinessProbe, | ||
*ProbeMask(&corev1.Probe{})).ViaField("readinessProbe")) | ||
cfg := config.FromContextOrDefaults(ctx) | ||
if cfg.Features.MultiContainerProbing != config.Enabled { | ||
if container.LivenessProbe != nil { | ||
errs = errs.Also(apis.CheckDisallowedFields(*container.LivenessProbe, | ||
*ProbeMask(&corev1.Probe{})).ViaField("livenessProbe")) | ||
} | ||
if container.ReadinessProbe != nil { | ||
errs = errs.Also(apis.CheckDisallowedFields(*container.ReadinessProbe, | ||
*ProbeMask(&corev1.Probe{})).ViaField("readinessProbe")) | ||
} | ||
} else if cfg.Features.MultiContainerProbing == config.Enabled { | ||
// Liveness Probes | ||
errs = errs.Also(validateProbe(container.LivenessProbe, nil, false).ViaField("livenessProbe")) | ||
// Readiness Probes | ||
errs = errs.Also(validateReadinessProbe(container.ReadinessProbe, nil, false).ViaField("readinessProbe")) | ||
} | ||
|
||
return errs.Also(validate(ctx, container, volumes)) | ||
} | ||
|
||
|
@@ -544,12 +553,12 @@ func validateInitContainer(ctx context.Context, container corev1.Container, volu | |
return errs.Also(validate(WithinInitContainer(ctx), container, volumes)) | ||
} | ||
|
||
// ValidateContainer validate fields for serving containers | ||
func ValidateContainer(ctx context.Context, container corev1.Container, volumes map[string]corev1.Volume, port corev1.ContainerPort) (errs *apis.FieldError) { | ||
// ValidateUserContainer validate fields for serving containers | ||
func ValidateUserContainer(ctx context.Context, container corev1.Container, volumes map[string]corev1.Volume, port corev1.ContainerPort) (errs *apis.FieldError) { | ||
// Liveness Probes | ||
errs = errs.Also(validateProbe(container.LivenessProbe, port).ViaField("livenessProbe")) | ||
errs = errs.Also(validateProbe(container.LivenessProbe, &port, true).ViaField("livenessProbe")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could also pass context to validateProbe and define a simple func IsUserContainer(ctx). We use that pattern elsewhere in this file. In this case it is simple to pass the flag but in general it might be better in cases where we have multiple conditions. 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, maybe a bit overkill here for one private only bool param 🤷 |
||
// Readiness Probes | ||
errs = errs.Also(validateReadinessProbe(container.ReadinessProbe, port).ViaField("readinessProbe")) | ||
errs = errs.Also(validateReadinessProbe(container.ReadinessProbe, &port, true).ViaField("readinessProbe")) | ||
return errs.Also(validate(ctx, container, volumes)) | ||
} | ||
|
||
|
@@ -751,12 +760,12 @@ func validateContainerPortBasic(port corev1.ContainerPort) *apis.FieldError { | |
return errs | ||
} | ||
|
||
func validateReadinessProbe(p *corev1.Probe, port corev1.ContainerPort) *apis.FieldError { | ||
func validateReadinessProbe(p *corev1.Probe, port *corev1.ContainerPort, isUserContainer bool) *apis.FieldError { | ||
if p == nil { | ||
return nil | ||
} | ||
|
||
errs := validateProbe(p, port) | ||
errs := validateProbe(p, port, isUserContainer) | ||
|
||
if p.PeriodSeconds < 0 { | ||
errs = errs.Also(apis.ErrOutOfBoundsValue(p.PeriodSeconds, 0, math.MaxInt32, "periodSeconds")) | ||
|
@@ -798,7 +807,7 @@ func validateReadinessProbe(p *corev1.Probe, port corev1.ContainerPort) *apis.Fi | |
return errs | ||
} | ||
|
||
func validateProbe(p *corev1.Probe, port corev1.ContainerPort) *apis.FieldError { | ||
func validateProbe(p *corev1.Probe, port *corev1.ContainerPort, isUserContainer bool) *apis.FieldError { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a few functions we could use to do some early checking from "k8s.io/apimachinery/pkg/util/validation", we already import that:
validation is the same pkg K8s project uses to do validation. So I am wondering if we want to fail fast and if so which part of the logic here: https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/core/validation/validation.go#L3057 we want to re-use or copy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably better addressed in a separate issue/PR? This one is about adding the same logic to multiple containers. When we use K8s functionality, we change also the existing validation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ReToCode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if p == nil { | ||
return nil | ||
} | ||
|
@@ -813,16 +822,28 @@ func validateProbe(p *corev1.Probe, port corev1.ContainerPort) *apis.FieldError | |
handlers = append(handlers, "httpGet") | ||
errs = errs.Also(apis.CheckDisallowedFields(*h.HTTPGet, *HTTPGetActionMask(h.HTTPGet))).ViaField("httpGet") | ||
getPort := h.HTTPGet.Port | ||
if getPort.StrVal != "" && getPort.StrVal != port.Name { | ||
errs = errs.Also(apis.ErrInvalidValue(getPort.String(), "httpGet.port", "Probe port must match container port")) | ||
if isUserContainer { | ||
if getPort.StrVal != "" && getPort.StrVal != port.Name { | ||
errs = errs.Also(apis.ErrInvalidValue(getPort.String(), "httpGet.port", "Probe port must match container port")) | ||
} | ||
} else { | ||
if getPort.StrVal == "" && getPort.IntVal == 0 { | ||
errs = errs.Also(apis.ErrInvalidValue(getPort.String(), "httpGet.port", "Probe port must be specified")) | ||
} | ||
} | ||
} | ||
if h.TCPSocket != nil { | ||
handlers = append(handlers, "tcpSocket") | ||
errs = errs.Also(apis.CheckDisallowedFields(*h.TCPSocket, *TCPSocketActionMask(h.TCPSocket))).ViaField("tcpSocket") | ||
tcpPort := h.TCPSocket.Port | ||
if tcpPort.StrVal != "" && tcpPort.StrVal != port.Name { | ||
errs = errs.Also(apis.ErrInvalidValue(tcpPort.String(), "tcpSocket.port", "Probe port must match container port")) | ||
if isUserContainer { | ||
if tcpPort.StrVal != "" && tcpPort.StrVal != port.Name { | ||
errs = errs.Also(apis.ErrInvalidValue(tcpPort.String(), "tcpSocket.port", "Probe port must match container port")) | ||
} | ||
} else { | ||
if tcpPort.StrVal == "" && tcpPort.IntVal == 0 { | ||
errs = errs.Also(apis.ErrInvalidValue(tcpPort.String(), "tcpSocket.port", "Probe port must be specified")) | ||
} | ||
} | ||
} | ||
if h.Exec != nil { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might not just want to enable this when
multi-container
is enabled, as it will change the QP spec (array instead of single probe) which causes a redeploy of all Knative Services, thus a new flag.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also it is a good idea for people to test this and then we should remove the flag once this is mature enough. We need to add this to the docs under https://knative.dev/docs/serving/configuration/feature-flags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added docs in knative/docs#5855