Skip to content
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

Promote EphemeralContainers to beta #105405

Merged
merged 11 commits into from Oct 20, 2021
1 change: 0 additions & 1 deletion api/api-rules/violation_exceptions.list
Expand Up @@ -90,7 +90,6 @@ API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommo
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,Command
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,Env
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,EnvFrom
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,Ports
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,VolumeDevices
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,VolumeMounts
API rule violation: list_type_missing,k8s.io/api/core/v1,ExecAction,Command
Expand Down
19 changes: 13 additions & 6 deletions api/openapi-spec/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions pkg/api/pod/util_test.go
Expand Up @@ -36,6 +36,7 @@ import (
)

func TestVisitContainers(t *testing.T) {
setAllFeatureEnabledContainersDuringTest := ContainerType(0)
testCases := []struct {
desc string
spec *api.PodSpec
Expand Down Expand Up @@ -142,7 +143,7 @@ func TestVisitContainers(t *testing.T) {
},
},
wantContainers: []string{"i1", "i2", "c1", "c2"},
mask: AllFeatureEnabledContainers(),
mask: setAllFeatureEnabledContainersDuringTest,
},
{
desc: "all feature enabled container types with ephemeral containers enabled",
Expand All @@ -161,7 +162,7 @@ func TestVisitContainers(t *testing.T) {
},
},
wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"},
mask: AllFeatureEnabledContainers(),
mask: setAllFeatureEnabledContainersDuringTest,
ephemeralContainersEnabled: true,
},
{
Expand All @@ -187,8 +188,9 @@ func TestVisitContainers(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
if tc.ephemeralContainersEnabled {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, tc.ephemeralContainersEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, tc.ephemeralContainersEnabled)()

if tc.mask == setAllFeatureEnabledContainersDuringTest {
tc.mask = AllFeatureEnabledContainers()
}

Expand Down
10 changes: 6 additions & 4 deletions pkg/api/v1/pod/util_test.go
Expand Up @@ -203,6 +203,7 @@ func TestFindPort(t *testing.T) {
}

func TestVisitContainers(t *testing.T) {
setAllFeatureEnabledContainersDuringTest := ContainerType(0)
testCases := []struct {
desc string
spec *v1.PodSpec
Expand Down Expand Up @@ -309,7 +310,7 @@ func TestVisitContainers(t *testing.T) {
},
},
wantContainers: []string{"i1", "i2", "c1", "c2"},
mask: AllFeatureEnabledContainers(),
mask: setAllFeatureEnabledContainersDuringTest,
},
{
desc: "all feature enabled container types with ephemeral containers enabled",
Expand All @@ -328,7 +329,7 @@ func TestVisitContainers(t *testing.T) {
},
},
wantContainers: []string{"i1", "i2", "c1", "c2", "e1", "e2"},
mask: AllFeatureEnabledContainers(),
mask: setAllFeatureEnabledContainersDuringTest,
ephemeralContainersEnabled: true,
},
{
Expand All @@ -354,8 +355,9 @@ func TestVisitContainers(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
if tc.ephemeralContainersEnabled {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, tc.ephemeralContainersEnabled)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, tc.ephemeralContainersEnabled)()

if tc.mask == setAllFeatureEnabledContainersDuringTest {
tc.mask = AllFeatureEnabledContainers()
}

Expand Down
28 changes: 16 additions & 12 deletions pkg/apis/core/types.go
Expand Up @@ -2749,7 +2749,7 @@ type PodSpec struct {
// pod to perform user-initiated actions such as debugging. This list cannot be specified when
// creating a pod, and it cannot be modified by updating the pod spec. In order to add an
// ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource.
// This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature.
// This field is beta-level and available on clusters that haven't disabled the EphemeralContainers feature gate.
// +optional
EphemeralContainers []EphemeralContainer
// +optional
Expand Down Expand Up @@ -3137,6 +3137,7 @@ type EphemeralContainerCommon struct {
// already allocated to the pod.
// +optional
Resources ResourceRequirements
// Pod volumes to mount into the container's filesystem. Subpath mounts are not allowed for ephemeral containers.
// +optional
VolumeMounts []VolumeMount
// volumeDevices is the list of block devices to be used by the container.
Expand Down Expand Up @@ -3180,15 +3181,16 @@ type EphemeralContainerCommon struct {
// these two types.
var _ = Container(EphemeralContainerCommon{})

// An EphemeralContainer is a temporary container that may be added to an existing pod for
// An EphemeralContainer is a temporary container that you may add to an existing Pod for
// user-initiated activities such as debugging. Ephemeral containers have no resource or
// scheduling guarantees, and they will not be restarted when they exit or when a pod is
// removed or restarted. If an ephemeral container causes a pod to exceed its resource
// allocation, the pod may be evicted.
// Ephemeral containers may not be added by directly updating the pod spec. They must be added
// via the pod's ephemeralcontainers subresource, and they will appear in the pod spec
// once added.
// This is an alpha feature enabled by the EphemeralContainers feature flag.
// scheduling guarantees, and they will not be restarted when they exit or when a Pod is
// removed or restarted. The kubelet may evict a Pod if an ephemeral container causes the
// Pod to exceed its resource allocation.
//
// To add an ephemeral container, use the ephemeralcontainers subresource of an existing
// Pod. Ephemeral containers may not be removed or restarted.
//
// This is a beta feature available on clusters that haven't disabled the EphemeralContainers feature gate.
type EphemeralContainer struct {
// Ephemeral containers have all of the fields of Container, plus additional fields
// specific to ephemeral containers. Fields in common with Container are in the
Expand All @@ -3198,8 +3200,10 @@ type EphemeralContainer struct {

// If set, the name of the container from PodSpec that this ephemeral container targets.
// The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container.
// If not set then the ephemeral container is run in whatever namespaces are shared
// for the pod. Note that the container runtime must support this feature.
// If not set then the ephemeral container uses the namespaces configured in the Pod spec.
//
// The container runtime must implement support for this feature. If the runtime does not
// support namespace targeting then the result of setting this field is undefined.
// +optional
TargetContainerName string
}
Expand Down Expand Up @@ -3253,7 +3257,7 @@ type PodStatus struct {
ContainerStatuses []ContainerStatus

// Status for any ephemeral containers that have run in this pod.
// This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature.
// This field is beta-level and available on clusters that haven't disabled the EphemeralContainers feature gate.
// +optional
EphemeralContainerStatuses []ContainerStatus
}
Expand Down
41 changes: 41 additions & 0 deletions pkg/apis/core/types_test.go
@@ -0,0 +1,41 @@
/*
Copyright 2021 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package core

import (
"reflect"
"testing"
)

// TestEphemeralContainer ensures that the tags of Container and EphemeralContainerCommon are kept in sync.
func TestEphemeralContainer(t *testing.T) {
ephemeralType := reflect.TypeOf(EphemeralContainerCommon{})
containerType := reflect.TypeOf(Container{})

ephemeralFields := ephemeralType.NumField()
containerFields := containerType.NumField()
if containerFields != ephemeralFields {
t.Fatalf("%v has %d fields, %v has %d fields", ephemeralType, ephemeralFields, containerType, containerFields)
}
for i := 0; i < ephemeralFields; i++ {
ephemeralField := ephemeralType.Field(i)
containerField := containerType.Field(i)
if !reflect.DeepEqual(ephemeralField, containerField) {
t.Errorf("field %v differs:\n\t%#v\n\t%#v", ephemeralField.Name, ephemeralField, containerField)
}
}
}
23 changes: 22 additions & 1 deletion pkg/apis/core/validation/validation.go
Expand Up @@ -79,9 +79,16 @@ var allowedEphemeralContainerFields = map[string]bool{
"Command": true,
"Args": true,
"WorkingDir": true,
"Ports": false,
"EnvFrom": true,
"Env": true,
"Resources": false,
"VolumeMounts": true,
"VolumeDevices": true,
"LivenessProbe": false,
"ReadinessProbe": false,
"StartupProbe": false,
"Lifecycle": false,
"TerminationMessagePath": true,
"TerminationMessagePolicy": true,
"ImagePullPolicy": true,
Expand Down Expand Up @@ -2873,6 +2880,18 @@ func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer,
// Lifecycle, probes, resources and ports should be disallowed. This is implemented as a list
// of allowed fields so that new fields will be given consideration prior to inclusion in Ephemeral Containers.
allErrs = append(allErrs, validateFieldAllowList(ec.EphemeralContainerCommon, allowedEphemeralContainerFields, "cannot be set for an Ephemeral Container", idxPath)...)

// VolumeMount subpaths have the potential to leak resources since they're implemented with bind mounts
// that aren't cleaned up until the pod exits. Since they also imply that the container is being used
// as part of the workload, they're disallowed entirely.
for i, vm := range ec.VolumeMounts {
if vm.SubPath != "" {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("volumeMounts").Index(i).Child("subPath"), "cannot be set for an Ephemeral Container"))
}
if vm.SubPathExpr != "" {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("volumeMounts").Index(i).Child("subPathExpr"), "cannot be set for an Ephemeral Container"))
}
}
}

return allErrs
Expand Down Expand Up @@ -4165,7 +4184,7 @@ func ValidateContainerStateTransition(newStatuses, oldStatuses []core.ContainerS
return allErrs
}

// ValidatePodStatusUpdate tests to see if the update is legal for an end user to make.
// ValidatePodStatusUpdate checks for changes to status that shouldn't occur in normal operation.
func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList {
fldPath := field.NewPath("metadata")
allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath)
Expand All @@ -4187,6 +4206,8 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions
// any terminated containers to a non-terminated state.
allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.ContainerStatuses, oldPod.Status.ContainerStatuses, fldPath.Child("containerStatuses"), oldPod.Spec.RestartPolicy)...)
allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.InitContainerStatuses, oldPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), oldPod.Spec.RestartPolicy)...)
// The kubelet will never restart ephemeral containers, so treat them like they have an implicit RestartPolicyNever.
allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.EphemeralContainerStatuses, oldPod.Status.EphemeralContainerStatuses, fldPath.Child("ephemeralContainerStatuses"), core.RestartPolicyNever)...)

if newIPErrs := validatePodIPs(newPod); len(newIPErrs) > 0 {
allErrs = append(allErrs, newIPErrs...)
Expand Down