Skip to content

Commit 0e14098

Browse files
authored
Merge pull request kubernetes#116429 from SergeyKanzhelev/sidecar
Add SidecarContainers feature
2 parents 2d42274 + 7286d12 commit 0e14098

File tree

107 files changed

+5909
-1234
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+5909
-1234
lines changed

api/openapi-spec/swagger.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/openapi-spec/v3/api__v1_openapi.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,10 @@
11791179
"default": {},
11801180
"description": "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
11811181
},
1182+
"restartPolicy": {
1183+
"description": "RestartPolicy defines the restart behavior of individual containers in a pod. This field may only be set for init containers, and the only allowed value is \"Always\". For non-init containers or when this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. Setting the RestartPolicy as \"Always\" for the init container will have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy \"Always\" will be shut down. This lifecycle differs from normal init containers and is often referred to as a \"sidecar\" container. Although this init container still starts in the init container sequence, it does not wait for the container to complete before proceeding to the next init container. Instead, the next init container starts immediately after this init container is started, or after any startupProbe has successfully completed.",
1184+
"type": "string"
1185+
},
11821186
"securityContext": {
11831187
"allOf": [
11841188
{
@@ -2029,6 +2033,10 @@
20292033
"default": {},
20302034
"description": "Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod."
20312035
},
2036+
"restartPolicy": {
2037+
"description": "Restart policy for the container to manage the restart behavior of each container within a pod. This may only be set for init containers. You cannot set this field on ephemeral containers.",
2038+
"type": "string"
2039+
},
20322040
"securityContext": {
20332041
"allOf": [
20342042
{

api/openapi-spec/v3/apis__apps__v1_openapi.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,10 @@
18021802
"default": {},
18031803
"description": "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
18041804
},
1805+
"restartPolicy": {
1806+
"description": "RestartPolicy defines the restart behavior of individual containers in a pod. This field may only be set for init containers, and the only allowed value is \"Always\". For non-init containers or when this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. Setting the RestartPolicy as \"Always\" for the init container will have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy \"Always\" will be shut down. This lifecycle differs from normal init containers and is often referred to as a \"sidecar\" container. Although this init container still starts in the init container sequence, it does not wait for the container to complete before proceeding to the next init container. Instead, the next init container starts immediately after this init container is started, or after any startupProbe has successfully completed.",
1807+
"type": "string"
1808+
},
18051809
"securityContext": {
18061810
"allOf": [
18071811
{
@@ -2236,6 +2240,10 @@
22362240
"default": {},
22372241
"description": "Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod."
22382242
},
2243+
"restartPolicy": {
2244+
"description": "Restart policy for the container to manage the restart behavior of each container within a pod. This may only be set for init containers. You cannot set this field on ephemeral containers.",
2245+
"type": "string"
2246+
},
22392247
"securityContext": {
22402248
"allOf": [
22412249
{

api/openapi-spec/v3/apis__batch__v1_openapi.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,10 @@
10931093
"default": {},
10941094
"description": "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"
10951095
},
1096+
"restartPolicy": {
1097+
"description": "RestartPolicy defines the restart behavior of individual containers in a pod. This field may only be set for init containers, and the only allowed value is \"Always\". For non-init containers or when this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. Setting the RestartPolicy as \"Always\" for the init container will have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy \"Always\" will be shut down. This lifecycle differs from normal init containers and is often referred to as a \"sidecar\" container. Although this init container still starts in the init container sequence, it does not wait for the container to complete before proceeding to the next init container. Instead, the next init container starts immediately after this init container is started, or after any startupProbe has successfully completed.",
1098+
"type": "string"
1099+
},
10961100
"securityContext": {
10971101
"allOf": [
10981102
{
@@ -1527,6 +1531,10 @@
15271531
"default": {},
15281532
"description": "Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod."
15291533
},
1534+
"restartPolicy": {
1535+
"description": "Restart policy for the container to manage the restart behavior of each container within a pod. This may only be set for init containers. You cannot set this field on ephemeral containers.",
1536+
"type": "string"
1537+
},
15301538
"securityContext": {
15311539
"allOf": [
15321540
{

pkg/api/pod/util.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,14 @@ func dropDisabledFields(
510510
podSpec.EphemeralContainers[i].ResizePolicy = nil
511511
}
512512
}
513+
514+
if !utilfeature.DefaultFeatureGate.Enabled(features.SidecarContainers) && !restartableInitContainersInUse(oldPodSpec) {
515+
// Drop the RestartPolicy field of init containers.
516+
for i := range podSpec.InitContainers {
517+
podSpec.InitContainers[i].RestartPolicy = nil
518+
}
519+
// For other types of containers, validateContainers will handle them.
520+
}
513521
}
514522

515523
// dropDisabledPodStatusFields removes disabled fields from the pod status
@@ -778,6 +786,23 @@ func schedulingGatesInUse(podSpec *api.PodSpec) bool {
778786
return len(podSpec.SchedulingGates) != 0
779787
}
780788

789+
// restartableInitContainersInUse returns true if the pod spec is non-nil and
790+
// it has any init container with ContainerRestartPolicyAlways.
791+
func restartableInitContainersInUse(podSpec *api.PodSpec) bool {
792+
if podSpec == nil {
793+
return false
794+
}
795+
var inUse bool
796+
VisitContainers(podSpec, InitContainers, func(c *api.Container, containerType ContainerType) bool {
797+
if c.RestartPolicy != nil && *c.RestartPolicy == api.ContainerRestartPolicyAlways {
798+
inUse = true
799+
return false
800+
}
801+
return true
802+
})
803+
return inUse
804+
}
805+
781806
func hasInvalidLabelValueInAffinitySelector(spec *api.PodSpec) bool {
782807
if spec.Affinity != nil {
783808
if spec.Affinity.PodAffinity != nil {

pkg/api/pod/util_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2052,6 +2052,109 @@ func TestDropInPlacePodVerticalScaling(t *testing.T) {
20522052
}
20532053
}
20542054

2055+
func TestDropSidecarContainers(t *testing.T) {
2056+
containerRestartPolicyAlways := api.ContainerRestartPolicyAlways
2057+
2058+
podWithSidecarContainers := func() *api.Pod {
2059+
return &api.Pod{
2060+
Spec: api.PodSpec{
2061+
InitContainers: []api.Container{
2062+
{
2063+
Name: "c1",
2064+
Image: "image",
2065+
RestartPolicy: &containerRestartPolicyAlways,
2066+
},
2067+
},
2068+
},
2069+
}
2070+
}
2071+
2072+
podWithoutSidecarContainers := func() *api.Pod {
2073+
return &api.Pod{
2074+
Spec: api.PodSpec{
2075+
InitContainers: []api.Container{
2076+
{
2077+
Name: "c1",
2078+
Image: "image",
2079+
},
2080+
},
2081+
},
2082+
}
2083+
}
2084+
2085+
podInfo := []struct {
2086+
description string
2087+
hasSidecarContainer bool
2088+
pod func() *api.Pod
2089+
}{
2090+
{
2091+
description: "has a sidecar container",
2092+
hasSidecarContainer: true,
2093+
pod: podWithSidecarContainers,
2094+
},
2095+
{
2096+
description: "does not have a sidecar container",
2097+
hasSidecarContainer: false,
2098+
pod: podWithoutSidecarContainers,
2099+
},
2100+
{
2101+
description: "is nil",
2102+
hasSidecarContainer: false,
2103+
pod: func() *api.Pod { return nil },
2104+
},
2105+
}
2106+
2107+
for _, enabled := range []bool{true, false} {
2108+
for _, oldPodInfo := range podInfo {
2109+
for _, newPodInfo := range podInfo {
2110+
oldPodHasSidecarContainer, oldPod := oldPodInfo.hasSidecarContainer, oldPodInfo.pod()
2111+
newPodHasSidecarContainer, newPod := newPodInfo.hasSidecarContainer, newPodInfo.pod()
2112+
if newPod == nil {
2113+
continue
2114+
}
2115+
2116+
t.Run(fmt.Sprintf("feature enabled=%v, old pod %v, new pod %v", enabled, oldPodInfo.description, newPodInfo.description), func(t *testing.T) {
2117+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SidecarContainers, enabled)()
2118+
2119+
var oldPodSpec *api.PodSpec
2120+
if oldPod != nil {
2121+
oldPodSpec = &oldPod.Spec
2122+
}
2123+
dropDisabledFields(&newPod.Spec, nil, oldPodSpec, nil)
2124+
2125+
// old pod should never be changed
2126+
if !reflect.DeepEqual(oldPod, oldPodInfo.pod()) {
2127+
t.Errorf("old pod changed: %v", cmp.Diff(oldPod, oldPodInfo.pod()))
2128+
}
2129+
2130+
switch {
2131+
case enabled || oldPodHasSidecarContainer:
2132+
// new pod shouldn't change if feature enabled or if old pod has
2133+
// any sidecar container
2134+
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
2135+
t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod()))
2136+
}
2137+
case newPodHasSidecarContainer:
2138+
// new pod should be changed
2139+
if reflect.DeepEqual(newPod, newPodInfo.pod()) {
2140+
t.Errorf("new pod was not changed")
2141+
}
2142+
// new pod should not have any sidecar container
2143+
if !reflect.DeepEqual(newPod, podWithoutSidecarContainers()) {
2144+
t.Errorf("new pod has a sidecar container: %v", cmp.Diff(newPod, podWithoutSidecarContainers()))
2145+
}
2146+
default:
2147+
// new pod should not need to be changed
2148+
if !reflect.DeepEqual(newPod, newPodInfo.pod()) {
2149+
t.Errorf("new pod changed: %v", cmp.Diff(newPod, newPodInfo.pod()))
2150+
}
2151+
}
2152+
})
2153+
}
2154+
}
2155+
}
2156+
}
2157+
20552158
func TestMarkPodProposedForResize(t *testing.T) {
20562159
testCases := []struct {
20572160
desc string

pkg/api/v1/resource/helpers.go

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func PodRequests(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
6565
cs, found := containerStatuses[container.Name]
6666
if found {
6767
if pod.Status.Resize == v1.PodResizeStatusInfeasible {
68-
containerReqs = cs.AllocatedResources
68+
containerReqs = cs.AllocatedResources.DeepCopy()
6969
} else {
7070
containerReqs = max(container.Resources.Requests, cs.AllocatedResources)
7171
}
@@ -83,20 +83,44 @@ func PodRequests(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
8383
addResourceList(reqs, containerReqs)
8484
}
8585

86+
restartableInitContainerReqs := v1.ResourceList{}
87+
initContainerReqs := v1.ResourceList{}
8688
// init containers define the minimum of any resource
8789
// Note: In-place resize is not allowed for InitContainers, so no need to check for ResizeStatus value
90+
//
91+
// Let's say `InitContainerUse(i)` is the resource requirements when the i-th
92+
// init container is initializing, then
93+
// `InitContainerUse(i) = sum(Resources of restartable init containers with index < i) + Resources of i-th init container`.
94+
//
95+
// See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#exposing-pod-resource-requirements for the detail.
8896
for _, container := range pod.Spec.InitContainers {
8997
containerReqs := container.Resources.Requests
9098
if len(opts.NonMissingContainerRequests) > 0 {
9199
containerReqs = applyNonMissing(containerReqs, opts.NonMissingContainerRequests)
92100
}
93101

102+
if container.RestartPolicy != nil && *container.RestartPolicy == v1.ContainerRestartPolicyAlways {
103+
// and add them to the resulting cumulative container requests
104+
addResourceList(reqs, containerReqs)
105+
106+
// track our cumulative restartable init container resources
107+
addResourceList(restartableInitContainerReqs, containerReqs)
108+
containerReqs = restartableInitContainerReqs
109+
} else {
110+
tmp := v1.ResourceList{}
111+
addResourceList(tmp, containerReqs)
112+
addResourceList(tmp, restartableInitContainerReqs)
113+
containerReqs = tmp
114+
}
115+
94116
if opts.ContainerFn != nil {
95117
opts.ContainerFn(containerReqs, podutil.InitContainers)
96118
}
97-
maxResourceList(reqs, containerReqs)
119+
maxResourceList(initContainerReqs, containerReqs)
98120
}
99121

122+
maxResourceList(reqs, initContainerReqs)
123+
100124
// Add overhead for running a pod to the sum of requests if requested:
101125
if !opts.ExcludeOverhead && pod.Spec.Overhead != nil {
102126
addResourceList(reqs, pod.Spec.Overhead)
@@ -135,14 +159,40 @@ func PodLimits(pod *v1.Pod, opts PodResourcesOptions) v1.ResourceList {
135159
}
136160
addResourceList(limits, container.Resources.Limits)
137161
}
162+
163+
restartableInitContainerLimits := v1.ResourceList{}
164+
initContainerLimits := v1.ResourceList{}
138165
// init containers define the minimum of any resource
166+
//
167+
// Let's say `InitContainerUse(i)` is the resource requirements when the i-th
168+
// init container is initializing, then
169+
// `InitContainerUse(i) = sum(Resources of restartable init containers with index < i) + Resources of i-th init container`.
170+
//
171+
// See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/753-sidecar-containers#exposing-pod-resource-requirements for the detail.
139172
for _, container := range pod.Spec.InitContainers {
173+
containerLimits := container.Resources.Limits
174+
// Is the init container marked as a restartable init container?
175+
if container.RestartPolicy != nil && *container.RestartPolicy == v1.ContainerRestartPolicyAlways {
176+
addResourceList(limits, containerLimits)
177+
178+
// track our cumulative restartable init container resources
179+
addResourceList(restartableInitContainerLimits, containerLimits)
180+
containerLimits = restartableInitContainerLimits
181+
} else {
182+
tmp := v1.ResourceList{}
183+
addResourceList(tmp, containerLimits)
184+
addResourceList(tmp, restartableInitContainerLimits)
185+
containerLimits = tmp
186+
}
187+
140188
if opts.ContainerFn != nil {
141-
opts.ContainerFn(container.Resources.Limits, podutil.InitContainers)
189+
opts.ContainerFn(containerLimits, podutil.InitContainers)
142190
}
143-
maxResourceList(limits, container.Resources.Limits)
191+
maxResourceList(initContainerLimits, containerLimits)
144192
}
145193

194+
maxResourceList(limits, initContainerLimits)
195+
146196
// Add overhead to non-zero limits if requested:
147197
if !opts.ExcludeOverhead && pod.Spec.Overhead != nil {
148198
for name, quantity := range pod.Spec.Overhead {

0 commit comments

Comments
 (0)