Skip to content

Commit e706d27

Browse files
committed
kubeadm errors now ignorable via v1beta2 config files
Specifically, IgnorePreflightErrors in {Init,Join}Configuration's NodeRegistrationOptions can be used to achieve this. See also: https://docs.google.com/document/d/1XnP67oO1i9VcDIpw42IzptnJsc5OQM-HTf8cVcjCR2w/edit
1 parent d83805c commit e706d27

File tree

14 files changed

+311
-48
lines changed

14 files changed

+311
-48
lines changed

cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
3232
fuzzClusterConfiguration,
3333
fuzzComponentConfigs,
3434
fuzzDNS,
35+
fuzzNodeRegistration,
3536
fuzzLocalEtcd,
3637
fuzzNetworking,
3738
fuzzJoinConfiguration,
@@ -87,6 +88,13 @@ func fuzzInitConfiguration(obj *kubeadm.InitConfiguration, c fuzz.Continue) {
8788
obj.CertificateKey = ""
8889
}
8990

91+
func fuzzNodeRegistration(obj *kubeadm.NodeRegistrationOptions, c fuzz.Continue) {
92+
c.FuzzNoCustom(obj)
93+
94+
// Pinning values for fields that get defaults if fuzz value is empty string or nil (thus making the round trip test fail)
95+
obj.IgnorePreflightErrors = nil
96+
}
97+
9098
func fuzzClusterConfiguration(obj *kubeadm.ClusterConfiguration, c fuzz.Continue) {
9199
c.FuzzNoCustom(obj)
92100

cmd/kubeadm/app/apis/kubeadm/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ type NodeRegistrationOptions struct {
229229
// kubeadm writes at runtime for the kubelet to source. This overrides the generic base-level configuration in the kubelet-config-1.X ConfigMap
230230
// Flags have higher priority when parsing. These values are local and specific to the node kubeadm is executing on.
231231
KubeletExtraArgs map[string]string
232+
233+
// IgnorePreflightErrors provides a slice of pre-flight errors to be ignored when the current node is registered.
234+
IgnorePreflightErrors []string
232235
}
233236

234237
// Networking contains elements describing cluster's networking configuration.

cmd/kubeadm/app/apis/kubeadm/v1beta1/conversion.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,15 @@ func Convert_kubeadm_JoinControlPlane_To_v1beta1_JoinControlPlane(in *kubeadm.Jo
4545

4646
return nil
4747
}
48+
49+
func Convert_kubeadm_NodeRegistrationOptions_To_v1beta1_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error {
50+
if err := autoConvert_kubeadm_NodeRegistrationOptions_To_v1beta1_NodeRegistrationOptions(in, out, s); err != nil {
51+
return err
52+
}
53+
54+
if len(in.IgnorePreflightErrors) > 0 {
55+
return errors.New("ignorePreflightErrors field is not supported by v1beta1 config format")
56+
}
57+
58+
return nil
59+
}

cmd/kubeadm/app/apis/kubeadm/v1beta1/conversion_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ func TestInternalToVersionedInitConfigurationConversion(t *testing.T) {
3737
},
3838
expectedError: true,
3939
},
40+
"ignorePreflightErrors set causes an error": {
41+
in: kubeadm.InitConfiguration{
42+
NodeRegistration: kubeadm.NodeRegistrationOptions{
43+
IgnorePreflightErrors: []string{"SomeUndesirableError"},
44+
},
45+
},
46+
expectedError: true,
47+
},
4048
}
4149
for name, tc := range testcases {
4250
t.Run(name, func(t *testing.T) {
@@ -51,6 +59,66 @@ func TestInternalToVersionedInitConfigurationConversion(t *testing.T) {
5159
}
5260
}
5361

62+
func TestInternalToVersionedJoinConfigurationConversion(t *testing.T) {
63+
testcases := map[string]struct {
64+
in kubeadm.JoinConfiguration
65+
expectedError bool
66+
}{
67+
"conversion succeeds": {
68+
in: kubeadm.JoinConfiguration{},
69+
expectedError: false,
70+
},
71+
"ignorePreflightErrors set causes an error": {
72+
in: kubeadm.JoinConfiguration{
73+
NodeRegistration: kubeadm.NodeRegistrationOptions{
74+
IgnorePreflightErrors: []string{"SomeUndesirableError"},
75+
},
76+
},
77+
expectedError: true,
78+
},
79+
}
80+
for name, tc := range testcases {
81+
t.Run(name, func(t *testing.T) {
82+
versioned := &JoinConfiguration{}
83+
err := Convert_kubeadm_JoinConfiguration_To_v1beta1_JoinConfiguration(&tc.in, versioned, nil)
84+
if err == nil && tc.expectedError {
85+
t.Error("unexpected success")
86+
} else if err != nil && !tc.expectedError {
87+
t.Errorf("unexpected error: %v", err)
88+
}
89+
})
90+
}
91+
}
92+
93+
func TestInternalToVersionedNodeRegistrationOptionsConversion(t *testing.T) {
94+
testcases := map[string]struct {
95+
in kubeadm.NodeRegistrationOptions
96+
expectedError bool
97+
}{
98+
"conversion succeeds": {
99+
in: kubeadm.NodeRegistrationOptions{},
100+
expectedError: false,
101+
},
102+
"ignorePreflightErrors set causes an error": {
103+
in: kubeadm.NodeRegistrationOptions{
104+
IgnorePreflightErrors: []string{"SomeUndesirableError"},
105+
},
106+
expectedError: true,
107+
},
108+
}
109+
for name, tc := range testcases {
110+
t.Run(name, func(t *testing.T) {
111+
versioned := &NodeRegistrationOptions{}
112+
err := Convert_kubeadm_NodeRegistrationOptions_To_v1beta1_NodeRegistrationOptions(&tc.in, versioned, nil)
113+
if err == nil && tc.expectedError {
114+
t.Error("unexpected success")
115+
} else if err != nil && !tc.expectedError {
116+
t.Errorf("unexpected error: %v", err)
117+
}
118+
})
119+
}
120+
}
121+
54122
func TestInternalToVersionedJoinControlPlaneConversion(t *testing.T) {
55123
testcases := map[string]struct {
56124
in kubeadm.JoinControlPlane

cmd/kubeadm/app/apis/kubeadm/v1beta2/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ type NodeRegistrationOptions struct {
215215
// kubeadm writes at runtime for the kubelet to source. This overrides the generic base-level configuration in the kubelet-config-1.X ConfigMap
216216
// Flags have higher priority when parsing. These values are local and specific to the node kubeadm is executing on.
217217
KubeletExtraArgs map[string]string `json:"kubeletExtraArgs,omitempty"`
218+
219+
// IgnorePreflightErrors provides a slice of pre-flight errors to be ignored when the current node is registered.
220+
IgnorePreflightErrors []string `json:"ignorePreflightErrors,omitempty"`
218221
}
219222

220223
// Networking contains elements describing cluster's networking configuration

cmd/kubeadm/app/apis/kubeadm/validation/validation.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,12 +463,25 @@ func ValidateAPIEndpoint(c *kubeadm.APIEndpoint, fldPath *field.Path) field.Erro
463463
return allErrs
464464
}
465465

466-
// ValidateIgnorePreflightErrors validates duplicates in ignore-preflight-errors flag.
467-
func ValidateIgnorePreflightErrors(ignorePreflightErrors []string) (sets.String, error) {
466+
// ValidateIgnorePreflightErrors validates duplicates in:
467+
// - ignore-preflight-errors flag and
468+
// - ignorePreflightErrors field in {Init,Join}Configuration files.
469+
func ValidateIgnorePreflightErrors(ignorePreflightErrorsFromCLI, ignorePreflightErrorsFromConfigFile []string) (sets.String, error) {
468470
ignoreErrors := sets.NewString()
469471
allErrs := field.ErrorList{}
470472

471-
for _, item := range ignorePreflightErrors {
473+
for _, item := range ignorePreflightErrorsFromConfigFile {
474+
ignoreErrors.Insert(strings.ToLower(item)) // parameters are case insensitive
475+
}
476+
477+
if ignoreErrors.Has("all") {
478+
// "all" is forbidden in config files. Administrators should use an
479+
// explicit list of errors they want to ignore, as it can be risky to
480+
// mask all errors in such a way. Hence, we return an error:
481+
allErrs = append(allErrs, field.Invalid(field.NewPath("ignorePreflightErrors"), "all", "'all' cannot be used in configuration file"))
482+
}
483+
484+
for _, item := range ignorePreflightErrorsFromCLI {
472485
ignoreErrors.Insert(strings.ToLower(item)) // parameters are case insensitive
473486
}
474487

cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/spf13/pflag"
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/util/sets"
2728
"k8s.io/apimachinery/pkg/util/validation/field"
2829
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
2930
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
@@ -703,26 +704,81 @@ func TestValidateFeatureGates(t *testing.T) {
703704

704705
func TestValidateIgnorePreflightErrors(t *testing.T) {
705706
var tests = []struct {
706-
ignorePreflightErrors []string
707-
expectedLen int
708-
expectedError bool
707+
ignorePreflightErrorsFromCLI []string
708+
ignorePreflightErrorsFromConfigFile []string
709+
expectedSet sets.String
710+
expectedError bool
709711
}{
710-
{[]string{}, 0, false}, // empty list
711-
{[]string{"check1", "check2"}, 2, false}, // non-duplicate
712-
{[]string{"check1", "check2", "check1"}, 2, false}, // duplicates
713-
{[]string{"check1", "check2", "all"}, 3, true}, // non-duplicate, but 'all' present together wth individual checks
714-
{[]string{"all"}, 1, false}, // skip all checks by using new flag
715-
{[]string{"all"}, 1, false}, // skip all checks by using both old and new flags at the same time
712+
{ // empty lists in CLI and config file
713+
[]string{},
714+
[]string{},
715+
sets.NewString(),
716+
false,
717+
},
718+
{ // empty list in CLI only
719+
[]string{},
720+
[]string{"a"},
721+
sets.NewString("a"),
722+
false,
723+
},
724+
{ // empty list in config file only
725+
[]string{"a"},
726+
[]string{},
727+
sets.NewString("a"),
728+
false,
729+
},
730+
{ // no duplicates, no overlap
731+
[]string{"a", "b"},
732+
[]string{"c", "d"},
733+
sets.NewString("a", "b", "c", "d"),
734+
false,
735+
},
736+
{ // some duplicates, with some overlapping duplicates
737+
[]string{"a", "b", "a"},
738+
[]string{"c", "b"},
739+
sets.NewString("a", "b", "c"),
740+
false,
741+
},
742+
{ // non-duplicate, but 'all' present together with individual checks in CLI
743+
[]string{"a", "b", "all"},
744+
[]string{},
745+
sets.NewString(),
746+
true,
747+
},
748+
{ // empty list in CLI, but 'all' present in config file, which is forbidden
749+
[]string{},
750+
[]string{"all"},
751+
sets.NewString(),
752+
true,
753+
},
754+
{ // non-duplicate, but 'all' present in config file, which is forbidden
755+
[]string{"a", "b"},
756+
[]string{"all"},
757+
sets.NewString(),
758+
true,
759+
},
760+
{ // non-duplicate, but 'all' present in CLI, while values are in config file, which is forbidden
761+
[]string{"all"},
762+
[]string{"a", "b"},
763+
sets.NewString(),
764+
true,
765+
},
766+
{ // skip all checks
767+
[]string{"all"},
768+
[]string{},
769+
sets.NewString("all"),
770+
false,
771+
},
716772
}
717773
for _, rt := range tests {
718-
result, err := ValidateIgnorePreflightErrors(rt.ignorePreflightErrors)
774+
result, err := ValidateIgnorePreflightErrors(rt.ignorePreflightErrorsFromCLI, rt.ignorePreflightErrorsFromConfigFile)
719775
switch {
720776
case err != nil && !rt.expectedError:
721-
t.Errorf("ValidateIgnorePreflightErrors: unexpected error for input (%s), error: %v", rt.ignorePreflightErrors, err)
777+
t.Errorf("ValidateIgnorePreflightErrors: unexpected error for input (%s, %s), error: %v", rt.ignorePreflightErrorsFromCLI, rt.ignorePreflightErrorsFromConfigFile, err)
722778
case err == nil && rt.expectedError:
723-
t.Errorf("ValidateIgnorePreflightErrors: expected error for input (%s) but got: %v", rt.ignorePreflightErrors, result)
724-
case result.Len() != rt.expectedLen:
725-
t.Errorf("ValidateIgnorePreflightErrors: expected Len = %d for input (%s) but got: %v, %v", rt.expectedLen, rt.ignorePreflightErrors, result.Len(), result)
779+
t.Errorf("ValidateIgnorePreflightErrors: expected error for input (%s, %s) but got: %v", rt.ignorePreflightErrorsFromCLI, rt.ignorePreflightErrorsFromConfigFile, result)
780+
case err == nil && !result.Equal(rt.expectedSet):
781+
t.Errorf("ValidateIgnorePreflightErrors: expected (%v) for input (%s, %s) but got: %v", rt.expectedSet, rt.ignorePreflightErrorsFromCLI, rt.ignorePreflightErrorsFromConfigFile, result)
726782
}
727783
}
728784
}

cmd/kubeadm/app/cmd/init.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,6 @@ func newInitData(cmd *cobra.Command, args []string, options *initOptions, out io
296296
return nil, err
297297
}
298298

299-
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(options.ignorePreflightErrors)
300-
if err != nil {
301-
return nil, err
302-
}
303-
304299
if err = validation.ValidateMixedArguments(cmd.Flags()); err != nil {
305300
return nil, err
306301
}
@@ -316,6 +311,13 @@ func newInitData(cmd *cobra.Command, args []string, options *initOptions, out io
316311
return nil, err
317312
}
318313

314+
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(options.ignorePreflightErrors, cfg.NodeRegistration.IgnorePreflightErrors)
315+
if err != nil {
316+
return nil, err
317+
}
318+
// Also set the union of pre-flight errors to InitConfiguration, to provide a consistent view of the runtime configuration:
319+
cfg.NodeRegistration.IgnorePreflightErrors = ignorePreflightErrorsSet.List()
320+
319321
// override node name and CRI socket from the command line options
320322
if options.externalcfg.NodeRegistration.Name != "" {
321323
cfg.NodeRegistration.Name = options.externalcfg.NodeRegistration.Name

cmd/kubeadm/app/cmd/init_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"path/filepath"
2424
"testing"
2525

26+
"k8s.io/apimachinery/pkg/util/sets"
2627
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
2728
"k8s.io/kubernetes/cmd/kubeadm/app/features"
2829
)
@@ -38,6 +39,9 @@ bootstrapTokens:
3839
nodeRegistration:
3940
criSocket: /run/containerd/containerd.sock
4041
name: someName
42+
ignorePreflightErrors:
43+
- c
44+
- d
4145
---
4246
apiVersion: kubeadm.k8s.io/v1beta2
4347
kind: ClusterConfiguration
@@ -129,6 +133,30 @@ func TestNewInitData(t *testing.T) {
129133
},
130134
expectError: true,
131135
},
136+
137+
// Pre-flight errors:
138+
{
139+
name: "pre-flights errors from CLI args only",
140+
flags: map[string]string{
141+
options.IgnorePreflightErrors: "a,b",
142+
},
143+
validate: expectedInitIgnorePreflightErrors("a", "b"),
144+
},
145+
{
146+
name: "pre-flights errors from InitConfiguration only",
147+
flags: map[string]string{
148+
options.CfgPath: configFilePath,
149+
},
150+
validate: expectedInitIgnorePreflightErrors("c", "d"),
151+
},
152+
{
153+
name: "pre-flights errors from both CLI args and InitConfiguration",
154+
flags: map[string]string{
155+
options.CfgPath: configFilePath,
156+
options.IgnorePreflightErrors: "a,b",
157+
},
158+
validate: expectedInitIgnorePreflightErrors("a", "b", "c", "d"),
159+
},
132160
}
133161
for _, tc := range testCases {
134162
t.Run(tc.name, func(t *testing.T) {
@@ -157,3 +185,15 @@ func TestNewInitData(t *testing.T) {
157185
})
158186
}
159187
}
188+
189+
func expectedInitIgnorePreflightErrors(expectedItems ...string) func(t *testing.T, data *initData) {
190+
expected := sets.NewString(expectedItems...)
191+
return func(t *testing.T, data *initData) {
192+
if !expected.Equal(data.ignorePreflightErrors) {
193+
t.Errorf("Invalid ignore preflight errors. Expected: %v. Actual: %v", expected.List(), data.ignorePreflightErrors.List())
194+
}
195+
if !expected.HasAll(data.cfg.NodeRegistration.IgnorePreflightErrors...) {
196+
t.Errorf("Invalid ignore preflight errors in InitConfiguration. Expected: %v. Actual: %v", expected.List(), data.cfg.NodeRegistration.IgnorePreflightErrors)
197+
}
198+
}
199+
}

cmd/kubeadm/app/cmd/join.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,7 @@ func newJoinData(cmd *cobra.Command, args []string, opt *joinOptions, out io.Wri
349349
}
350350
}
351351

352-
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(opt.ignorePreflightErrors)
353-
if err != nil {
354-
return nil, err
355-
}
356-
357-
if err = validation.ValidateMixedArguments(cmd.Flags()); err != nil {
352+
if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil {
358353
return nil, err
359354
}
360355

@@ -383,6 +378,13 @@ func newJoinData(cmd *cobra.Command, args []string, opt *joinOptions, out io.Wri
383378
return nil, err
384379
}
385380

381+
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(opt.ignorePreflightErrors, cfg.NodeRegistration.IgnorePreflightErrors)
382+
if err != nil {
383+
return nil, err
384+
}
385+
// Also set the union of pre-flight errors to JoinConfiguration, to provide a consistent view of the runtime configuration:
386+
cfg.NodeRegistration.IgnorePreflightErrors = ignorePreflightErrorsSet.List()
387+
386388
// override node name and CRI socket from the command line opt
387389
if opt.externalcfg.NodeRegistration.Name != "" {
388390
cfg.NodeRegistration.Name = opt.externalcfg.NodeRegistration.Name

0 commit comments

Comments
 (0)