Skip to content

Commit 462ee14

Browse files
authored
Merge pull request kubernetes#134345 from yuanwang04/restart-pod
Implement RestartAllContainers
2 parents 5a26d2a + 0b47a37 commit 462ee14

File tree

34 files changed

+3359
-88
lines changed

34 files changed

+3359
-88
lines changed

pkg/api/pod/util.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
428428
OldPodViolatesLegacyMatchLabelKeysValidation: false,
429429
AllowContainerRestartPolicyRules: utilfeature.DefaultFeatureGate.Enabled(features.ContainerRestartRules),
430430
AllowUserNamespacesWithVolumeDevices: false,
431+
// This also allows restart rules on sidecar containers.
432+
AllowRestartAllContainers: utilfeature.DefaultFeatureGate.Enabled(features.RestartAllContainersOnContainerExits),
431433
}
432434

433435
// If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate,
@@ -476,6 +478,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
476478
opts.AllowSidecarResizePolicy = opts.AllowSidecarResizePolicy || hasRestartableInitContainerResizePolicy(oldPodSpec)
477479

478480
opts.AllowContainerRestartPolicyRules = opts.AllowContainerRestartPolicyRules || containerRestartRulesInUse(oldPodSpec)
481+
opts.AllowRestartAllContainers = opts.AllowRestartAllContainers || restartAllContainersActionInUse(oldPodSpec)
479482

480483
// If old spec has userns and volume devices (doesn't work), we still allow
481484
// modifications to it.
@@ -1826,3 +1829,28 @@ func workloadRefInUse(podSpec *api.PodSpec) bool {
18261829

18271830
return podSpec.WorkloadRef != nil
18281831
}
1832+
1833+
func restartAllContainersActionInUse(oldPodSpec *api.PodSpec) bool {
1834+
if oldPodSpec == nil {
1835+
return false
1836+
}
1837+
for _, c := range oldPodSpec.Containers {
1838+
for _, rule := range c.RestartPolicyRules {
1839+
if rule.Action == api.ContainerRestartRuleActionRestartAllContainers {
1840+
return true
1841+
}
1842+
}
1843+
}
1844+
for _, c := range oldPodSpec.InitContainers {
1845+
for _, rule := range c.RestartPolicyRules {
1846+
if rule.Action == api.ContainerRestartRuleActionRestartAllContainers {
1847+
return true
1848+
}
1849+
}
1850+
// This feature also allows sidecar containers to have rules.
1851+
if c.RestartPolicy != nil && *c.RestartPolicy == api.ContainerRestartPolicyAlways && len(c.RestartPolicyRules) > 0 {
1852+
return true
1853+
}
1854+
}
1855+
return false
1856+
}

pkg/api/pod/util_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6511,3 +6511,83 @@ func TestDisabledWorkload(t *testing.T) {
65116511
})
65126512
}
65136513
}
6514+
6515+
func TestValidateRestartAllContainersOption(t *testing.T) {
6516+
policyAlways := api.ContainerRestartPolicyAlways
6517+
testCases := []struct {
6518+
name string
6519+
oldPodSpec *api.PodSpec
6520+
featureEnabled bool
6521+
want bool
6522+
}{
6523+
{
6524+
name: "feature enabled",
6525+
featureEnabled: true,
6526+
want: true,
6527+
},
6528+
{
6529+
name: "feature disabled",
6530+
featureEnabled: false,
6531+
want: false,
6532+
},
6533+
{
6534+
name: "old pod spec has container without action",
6535+
oldPodSpec: &api.PodSpec{
6536+
Containers: []api.Container{{
6537+
Name: "container",
6538+
}},
6539+
},
6540+
featureEnabled: false,
6541+
want: false,
6542+
},
6543+
{
6544+
name: "old pod spec has container with action",
6545+
oldPodSpec: &api.PodSpec{
6546+
Containers: []api.Container{{
6547+
Name: "container",
6548+
RestartPolicyRules: []api.ContainerRestartRule{{
6549+
Action: api.ContainerRestartRuleActionRestartAllContainers,
6550+
ExitCodes: &api.ContainerRestartRuleOnExitCodes{
6551+
Operator: api.ContainerRestartRuleOnExitCodesOpIn,
6552+
Values: []int32{42},
6553+
},
6554+
}},
6555+
}},
6556+
},
6557+
featureEnabled: false,
6558+
want: true,
6559+
},
6560+
{
6561+
name: "old pod spec has sidecar containers with rules",
6562+
oldPodSpec: &api.PodSpec{
6563+
InitContainers: []api.Container{{
6564+
RestartPolicy: &policyAlways,
6565+
RestartPolicyRules: []api.ContainerRestartRule{{
6566+
Action: api.ContainerRestartRuleActionRestartAllContainers,
6567+
ExitCodes: &api.ContainerRestartRuleOnExitCodes{
6568+
Operator: api.ContainerRestartRuleOnExitCodesOpIn,
6569+
Values: []int32{42},
6570+
},
6571+
}},
6572+
}},
6573+
},
6574+
featureEnabled: false,
6575+
want: true,
6576+
},
6577+
}
6578+
6579+
for _, tc := range testCases {
6580+
t.Run(tc.name, func(t *testing.T) {
6581+
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
6582+
features.ContainerRestartRules: tc.featureEnabled,
6583+
features.NodeDeclaredFeatures: tc.featureEnabled,
6584+
features.RestartAllContainersOnContainerExits: tc.featureEnabled,
6585+
})
6586+
// The new pod doesn't impact the outcome.
6587+
gotOptions := GetValidationOptionsFromPodSpecAndMeta(nil, tc.oldPodSpec, nil, nil)
6588+
if tc.want != gotOptions.AllowRestartAllContainers {
6589+
t.Errorf("unexpected diff, want: %v, got: %v", tc.want, gotOptions.AllowRestartAllContainers)
6590+
}
6591+
})
6592+
}
6593+
}

pkg/api/v1/pod/util.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,11 +418,16 @@ func IsContainerRestartable(pod v1.PodSpec, container v1.Container) bool {
418418
// level policy are specified, pod-level restart policy is used.
419419
func ContainerShouldRestart(container v1.Container, pod v1.PodSpec, exitCode int32) bool {
420420
if container.RestartPolicy != nil {
421-
rule, ok := findMatchingContainerRestartRule(container, exitCode)
421+
rule, ok := FindMatchingContainerRestartRule(container, exitCode)
422422
if ok {
423423
switch rule.Action {
424424
case v1.ContainerRestartRuleActionRestart:
425425
return true
426+
case v1.ContainerRestartRuleActionRestartAllContainers:
427+
if utilfeature.DefaultFeatureGate.Enabled(features.RestartAllContainersOnContainerExits) {
428+
return true
429+
}
430+
// If feature is not enabled, fallback to container-level policy.
426431
default:
427432
// Do nothing, fallback to container-level restart policy.
428433
}
@@ -454,10 +459,10 @@ func ContainerShouldRestart(container v1.Container, pod v1.PodSpec, exitCode int
454459
}
455460
}
456461

457-
// findMatchingContainerRestartRule returns a rule and true if the exitCode matched
462+
// FindMatchingContainerRestartRule returns a rule and true if the exitCode matched
458463
// one of the restart rules for the given container. Returns and empty rule and
459464
// false if no rules matched.
460-
func findMatchingContainerRestartRule(container v1.Container, exitCode int32) (rule v1.ContainerRestartRule, found bool) {
465+
func FindMatchingContainerRestartRule(container v1.Container, exitCode int32) (rule v1.ContainerRestartRule, found bool) {
461466
for _, rule := range container.RestartPolicyRules {
462467
if rule.ExitCodes != nil {
463468
exitCodeMatched := false
@@ -483,6 +488,30 @@ func findMatchingContainerRestartRule(container v1.Container, exitCode int32) (r
483488
return v1.ContainerRestartRule{}, false
484489
}
485490

491+
// AllContainersCouldRestart returns true if all containers could be restarted
492+
// for the given pod. This is true if any container has a RestartAllContainers
493+
// action.
494+
func AllContainersCouldRestart(pod *v1.PodSpec) bool {
495+
if pod == nil {
496+
return false
497+
}
498+
for _, container := range pod.InitContainers {
499+
for _, rule := range container.RestartPolicyRules {
500+
if rule.Action == v1.ContainerRestartRuleActionRestartAllContainers {
501+
return true
502+
}
503+
}
504+
}
505+
for _, container := range pod.Containers {
506+
for _, rule := range container.RestartPolicyRules {
507+
if rule.Action == v1.ContainerRestartRuleActionRestartAllContainers {
508+
return true
509+
}
510+
}
511+
}
512+
return false
513+
}
514+
486515
// CalculatePodStatusObservedGeneration calculates the observedGeneration for the pod status.
487516
// This is used to track the generation of the pod that was observed by the kubelet.
488517
// The observedGeneration is set to the pod's generation when the feature gate

pkg/api/v1/pod/util_test.go

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import (
2828
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929
"k8s.io/apimachinery/pkg/util/sets"
3030
"k8s.io/apimachinery/pkg/util/validation/field"
31+
utilfeature "k8s.io/apiserver/pkg/util/feature"
32+
featuregatetesting "k8s.io/component-base/featuregate/testing"
33+
"k8s.io/kubernetes/pkg/features"
3134
)
3235

3336
func TestVisitContainers(t *testing.T) {
@@ -1182,6 +1185,7 @@ func TestContainerHasRestartablePolicy(t *testing.T) {
11821185
name string
11831186
container v1.Container
11841187
podSpec v1.PodSpec
1188+
podStatus v1.PodStatus
11851189
exitCode int32
11861190
expected bool
11871191
}{
@@ -1219,6 +1223,23 @@ func TestContainerHasRestartablePolicy(t *testing.T) {
12191223
exitCode: 99,
12201224
expected: true,
12211225
},
1226+
{
1227+
name: "Rule: 'In' operator matches with 'RestartAllContainers' action",
1228+
container: v1.Container{
1229+
RestartPolicy: &containerRestartPolicyNever,
1230+
RestartPolicyRules: []v1.ContainerRestartRule{
1231+
{
1232+
Action: v1.ContainerRestartRuleActionRestartAllContainers,
1233+
ExitCodes: &v1.ContainerRestartRuleOnExitCodes{
1234+
Operator: v1.ContainerRestartRuleOnExitCodesOpIn,
1235+
Values: []int32{42, 50, 60},
1236+
},
1237+
},
1238+
},
1239+
},
1240+
exitCode: 42,
1241+
expected: true,
1242+
},
12221243
{
12231244
name: "Rule: 'In' operator does not match, should fall back to container policy",
12241245
container: v1.Container{
@@ -1311,6 +1332,11 @@ func TestContainerHasRestartablePolicy(t *testing.T) {
13111332
expected: true,
13121333
},
13131334
}
1335+
featuregatetesting.SetFeatureGatesDuringTest(t, utilfeature.DefaultFeatureGate, featuregatetesting.FeatureOverrides{
1336+
features.ContainerRestartRules: true,
1337+
features.NodeDeclaredFeatures: true,
1338+
features.RestartAllContainersOnContainerExits: true,
1339+
})
13141340

13151341
for _, tc := range testCases {
13161342
t.Run(tc.name, func(t *testing.T) {
@@ -1325,6 +1351,90 @@ func TestContainerHasRestartablePolicy(t *testing.T) {
13251351
}
13261352
}
13271353

1354+
func TestAllContainersCouldRestart(t *testing.T) {
1355+
restartPolicyAlways := v1.ContainerRestartPolicyAlways
1356+
1357+
testCases := []struct {
1358+
name string
1359+
podSpec *v1.PodSpec
1360+
expected bool
1361+
}{
1362+
{
1363+
name: "Pod with rules in init containers",
1364+
podSpec: &v1.PodSpec{
1365+
InitContainers: []v1.Container{
1366+
{
1367+
RestartPolicyRules: []v1.ContainerRestartRule{
1368+
{
1369+
Action: v1.ContainerRestartRuleActionRestartAllContainers,
1370+
ExitCodes: &v1.ContainerRestartRuleOnExitCodes{
1371+
Operator: v1.ContainerRestartRuleOnExitCodesOpIn,
1372+
Values: []int32{1},
1373+
},
1374+
},
1375+
},
1376+
},
1377+
},
1378+
},
1379+
expected: true,
1380+
},
1381+
{
1382+
name: "Pod with rules in sidecar containers",
1383+
podSpec: &v1.PodSpec{
1384+
InitContainers: []v1.Container{
1385+
{
1386+
RestartPolicy: &restartPolicyAlways,
1387+
RestartPolicyRules: []v1.ContainerRestartRule{
1388+
{
1389+
Action: v1.ContainerRestartRuleActionRestartAllContainers,
1390+
ExitCodes: &v1.ContainerRestartRuleOnExitCodes{
1391+
Operator: v1.ContainerRestartRuleOnExitCodesOpIn,
1392+
Values: []int32{1},
1393+
},
1394+
},
1395+
},
1396+
},
1397+
},
1398+
},
1399+
expected: true,
1400+
},
1401+
{
1402+
name: "Pod with rules in regular containers",
1403+
podSpec: &v1.PodSpec{
1404+
Containers: []v1.Container{
1405+
{
1406+
RestartPolicy: &restartPolicyAlways,
1407+
RestartPolicyRules: []v1.ContainerRestartRule{
1408+
{
1409+
Action: v1.ContainerRestartRuleActionRestartAllContainers,
1410+
ExitCodes: &v1.ContainerRestartRuleOnExitCodes{
1411+
Operator: v1.ContainerRestartRuleOnExitCodesOpIn,
1412+
Values: []int32{1},
1413+
},
1414+
},
1415+
},
1416+
},
1417+
},
1418+
},
1419+
expected: true,
1420+
},
1421+
{
1422+
name: "Pod without rules",
1423+
podSpec: &v1.PodSpec{},
1424+
expected: false,
1425+
},
1426+
}
1427+
1428+
for _, tc := range testCases {
1429+
t.Run(tc.name, func(t *testing.T) {
1430+
got := AllContainersCouldRestart(tc.podSpec)
1431+
if got != tc.expected {
1432+
t.Errorf("AllContainersRestarting() = %v, want %v", got, tc.expected)
1433+
}
1434+
})
1435+
}
1436+
}
1437+
13281438
func TestFindMatchingContainerRestartRule(t *testing.T) {
13291439
ruleIn := v1.ContainerRestartRule{
13301440
Action: v1.ContainerRestartRuleActionRestart,
@@ -1403,7 +1513,7 @@ func TestFindMatchingContainerRestartRule(t *testing.T) {
14031513

14041514
for _, tc := range testCases {
14051515
t.Run(tc.name, func(t *testing.T) {
1406-
rule, found := findMatchingContainerRestartRule(tc.container, tc.exitCode)
1516+
rule, found := FindMatchingContainerRestartRule(tc.container, tc.exitCode)
14071517
if found != tc.expectedFound {
14081518
t.Errorf("FindMatchingContainerRestartRule() found = %v, want %v", found, tc.expectedFound)
14091519
}

pkg/apis/core/types.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3154,6 +3154,8 @@ const (
31543154
// If both PodResizePending and PodResizeInProgress are set, it means that a new resize was
31553155
// requested in the middle of a previous pod resize that is still in progress.
31563156
PodResizeInProgress PodConditionType = "PodResizeInProgress"
3157+
// AllContainersRestarting indicates that all containers of the pod is being restarted.
3158+
AllContainersRestarting PodConditionType = "AllContainersRestarting"
31573159
)
31583160

31593161
// PodCondition represents pod's condition
@@ -3241,9 +3243,15 @@ type ContainerRestartRule struct {
32413243
// container exits.
32423244
type ContainerRestartRuleAction string
32433245

3244-
// The only valid action is Restart.
3246+
// These are valid restart rule actions.
32453247
const (
3248+
// The container will be restarted if the rule matches. Only valid on normal init container and
3249+
// regular containers. Not valid on sidecar containers and ephemeral containers.
32463250
ContainerRestartRuleActionRestart ContainerRestartRuleAction = "Restart"
3251+
// All containers (except ephemeral containers) inside the pod will be terminated and restarted.
3252+
// Valid on normal init container, sidecar containers, and regular containers. Not valid on
3253+
// ephemeral containers.
3254+
ContainerRestartRuleActionRestartAllContainers ContainerRestartRuleAction = "RestartAllContainers"
32473255
)
32483256

32493257
// ContainerRestartRuleOnExitCodes describes the condition

0 commit comments

Comments
 (0)