From f96ada0c04241c523923615fbdd5a650b5ac5bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20K=C3=A4ldstr=C3=B6m?= Date: Sun, 8 Jul 2018 21:27:53 +0300 Subject: [PATCH] Add a new package for handling all ComponentConfig-related code --- cmd/kubeadm/app/componentconfigs/defaults.go | 120 ++++++ .../app/componentconfigs/registrations.go | 148 ++++++++ cmd/kubeadm/app/componentconfigs/scheme.go | 41 ++ .../app/componentconfigs/validation.go | 46 +++ .../app/componentconfigs/validation_test.go | 351 ++++++++++++++++++ 5 files changed, 706 insertions(+) create mode 100644 cmd/kubeadm/app/componentconfigs/defaults.go create mode 100644 cmd/kubeadm/app/componentconfigs/registrations.go create mode 100644 cmd/kubeadm/app/componentconfigs/scheme.go create mode 100644 cmd/kubeadm/app/componentconfigs/validation.go create mode 100644 cmd/kubeadm/app/componentconfigs/validation_test.go diff --git a/cmd/kubeadm/app/componentconfigs/defaults.go b/cmd/kubeadm/app/componentconfigs/defaults.go new file mode 100644 index 00000000000..d9c5607e98b --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/defaults.go @@ -0,0 +1,120 @@ +/* +Copyright 2018 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 componentconfigs + +import ( + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha3" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubeletconfigv1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" +) + +const ( + // KubeproxyKubeConfigFileName defines the file name for the kube-proxy's KubeConfig file + KubeproxyKubeConfigFileName = "/var/lib/kube-proxy/kubeconfig.conf" +) + +// DefaultKubeProxyConfiguration assigns default values for the kube-proxy ComponentConfig +func DefaultKubeProxyConfiguration(internalcfg *kubeadmapi.MasterConfiguration) { + // IMPORTANT NOTE: If you're changing this code you should mirror it to cmd/kubeadm/app/apis/kubeadm/v1alpha2/defaults.go + // and cmd/kubeadm/app/apis/kubeadm/v1alpha3/conversion.go. TODO: Remove this requirement when v1alpha2 is removed. + externalproxycfg := &kubeproxyconfigv1alpha1.KubeProxyConfiguration{} + + // Do a roundtrip to the external version for defaulting + Scheme.Convert(internalcfg.ComponentConfigs.KubeProxy, externalproxycfg, nil) + + if externalproxycfg.ClusterCIDR == "" && internalcfg.Networking.PodSubnet != "" { + externalproxycfg.ClusterCIDR = internalcfg.Networking.PodSubnet + } + + if externalproxycfg.ClientConnection.KubeConfigFile == "" { + externalproxycfg.ClientConnection.KubeConfigFile = KubeproxyKubeConfigFileName + } + + // Run the rest of the kube-proxy defaulting code + kubeproxyconfigv1alpha1.SetDefaults_KubeProxyConfiguration(externalproxycfg) + + if internalcfg.ComponentConfigs.KubeProxy == nil { + internalcfg.ComponentConfigs.KubeProxy = &kubeproxyconfig.KubeProxyConfiguration{} + } + + // TODO: Figure out how to handle errors in defaulting code + // Go back to the internal version + Scheme.Convert(externalproxycfg, internalcfg.ComponentConfigs.KubeProxy, nil) +} + +// DefaultKubeletConfiguration assigns default values for the kubelet ComponentConfig +func DefaultKubeletConfiguration(internalcfg *kubeadmapi.MasterConfiguration) { + // IMPORTANT NOTE: If you're changing this code you should mirror it to cmd/kubeadm/app/apis/kubeadm/v1alpha2/defaults.go + // and cmd/kubeadm/app/apis/kubeadm/v1alpha3/conversion.go. TODO: Remove this requirement when v1alpha2 is removed. + externalkubeletcfg := &kubeletconfigv1beta1.KubeletConfiguration{} + + // Do a roundtrip to the external version for defaulting + Scheme.Convert(internalcfg.ComponentConfigs.Kubelet, externalkubeletcfg, nil) + + if externalkubeletcfg.StaticPodPath == "" { + externalkubeletcfg.StaticPodPath = kubeadmapiv1alpha3.DefaultManifestsDir + } + if externalkubeletcfg.ClusterDNS == nil { + dnsIP, err := constants.GetDNSIP(internalcfg.Networking.ServiceSubnet) + if err != nil { + externalkubeletcfg.ClusterDNS = []string{kubeadmapiv1alpha3.DefaultClusterDNSIP} + } else { + externalkubeletcfg.ClusterDNS = []string{dnsIP.String()} + } + } + if externalkubeletcfg.ClusterDomain == "" { + externalkubeletcfg.ClusterDomain = internalcfg.Networking.DNSDomain + } + + // Enforce security-related kubelet options + + // Require all clients to the kubelet API to have client certs signed by the cluster CA + externalkubeletcfg.Authentication.X509.ClientCAFile = kubeadmapiv1alpha3.DefaultCACertPath + externalkubeletcfg.Authentication.Anonymous.Enabled = utilpointer.BoolPtr(false) + + // On every client request to the kubelet API, execute a webhook (SubjectAccessReview request) to the API server + // and ask it whether the client is authorized to access the kubelet API + externalkubeletcfg.Authorization.Mode = kubeletconfigv1beta1.KubeletAuthorizationModeWebhook + + // Let clients using other authentication methods like ServiceAccount tokens also access the kubelet API + externalkubeletcfg.Authentication.Webhook.Enabled = utilpointer.BoolPtr(true) + + // Disable the readonly port of the kubelet, in order to not expose unnecessary information + externalkubeletcfg.ReadOnlyPort = 0 + + // Enables client certificate rotation for the kubelet + externalkubeletcfg.RotateCertificates = true + + // Serve a /healthz webserver on localhost:10248 that kubeadm can talk to + externalkubeletcfg.HealthzBindAddress = "127.0.0.1" + externalkubeletcfg.HealthzPort = utilpointer.Int32Ptr(10248) + + kubeletconfigv1beta1.SetDefaults_KubeletConfiguration(externalkubeletcfg) + + if internalcfg.ComponentConfigs.Kubelet == nil { + internalcfg.ComponentConfigs.Kubelet = &kubeletconfig.KubeletConfiguration{} + } + + // TODO: Figure out how to handle errors in defaulting code + // Go back to the internal version + Scheme.Convert(externalkubeletcfg, internalcfg.ComponentConfigs.Kubelet, nil) +} diff --git a/cmd/kubeadm/app/componentconfigs/registrations.go b/cmd/kubeadm/app/componentconfigs/registrations.go new file mode 100644 index 00000000000..994b12020a0 --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/registrations.go @@ -0,0 +1,148 @@ +/* +Copyright 2018 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 componentconfigs + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + kubeletconfigv1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" +) + +// AddToSchemeFunc is a function that adds known types and API GroupVersions to a scheme +type AddToSchemeFunc func(*runtime.Scheme) error + +// Registration is an object for registering a Kubernetes ComponentConfig type to be recognized and handled by kubeadm +type Registration struct { + // MarshalGroupVersion is the preferred external API version to use when marshalling the ComponentConfig + MarshalGroupVersion schema.GroupVersion + // AddToSchemeFuncs are a set of functions that register APIs to the scheme + AddToSchemeFuncs []AddToSchemeFunc + // DefaulterFunc is a function that based on the internal kubeadm configuration defaults the ComponentConfig struct + DefaulterFunc func(*kubeadmapi.MasterConfiguration) + // ValidateFunc is a function that should validate the ComponentConfig type embedded in the internal kubeadm config struct + ValidateFunc func(*kubeadmapi.MasterConfiguration, *field.Path) field.ErrorList + // EmptyValue holds a pointer to an empty struct of the internal ComponentConfig type + EmptyValue runtime.Object + // GetFromInternalConfig returns the pointer to the ComponentConfig API object from the internal kubeadm config struct + GetFromInternalConfig func(*kubeadmapi.MasterConfiguration) (runtime.Object, bool) + // SetToInternalConfig sets the pointer to a ComponentConfig API object embedded in the internal kubeadm config struct + SetToInternalConfig func(runtime.Object, *kubeadmapi.MasterConfiguration) bool +} + +// Marshal marshals obj to bytes for the current Registration +func (r Registration) Marshal(obj runtime.Object) ([]byte, error) { + return kubeadmutil.MarshalToYamlForCodecs(obj, r.MarshalGroupVersion, Codecs) +} + +// Unmarshal unmarshals the bytes to a runtime.Object using the Codecs registered in this Scheme +func (r Registration) Unmarshal(fileContent []byte) (runtime.Object, error) { + // Do a deepcopy of the empty value so we don't mutate it, which could lead to strange errors + obj := r.EmptyValue.DeepCopyObject() + + // Decode the file content into obj which is a pointer to an empty struct of the internal ComponentConfig + // object, using the componentconfig Codecs that knows about all APIs + if err := runtime.DecodeInto(Codecs.UniversalDecoder(), fileContent, obj); err != nil { + return nil, err + } + return obj, nil +} + +const ( + // KubeletConfigurationKind is the kind for the kubelet ComponentConfig + KubeletConfigurationKind RegistrationKind = "KubeletConfiguration" + // KubeProxyConfigurationKind is the kind for the kubelet ComponentConfig + KubeProxyConfigurationKind RegistrationKind = "KubeProxyConfiguration" +) + +// RegistrationKind is a string type to ensure not any string can be a key in the Registrations map +type RegistrationKind string + +// Registrations holds a set of ComponentConfig Registration objects, where the map key is the kind +type Registrations map[RegistrationKind]Registration + +// Known contains the known ComponentConfig registrations to kubeadm +var Known Registrations = map[RegistrationKind]Registration{ + KubeProxyConfigurationKind: { + // TODO: When a beta version of the kube-proxy ComponentConfig API is available, start using it + MarshalGroupVersion: kubeproxyconfigv1alpha1.SchemeGroupVersion, + AddToSchemeFuncs: []AddToSchemeFunc{kubeproxyconfig.AddToScheme, kubeproxyconfigv1alpha1.AddToScheme}, + DefaulterFunc: DefaultKubeProxyConfiguration, + ValidateFunc: ValidateKubeProxyConfiguration, + EmptyValue: &kubeproxyconfig.KubeProxyConfiguration{}, + GetFromInternalConfig: func(cfg *kubeadmapi.MasterConfiguration) (runtime.Object, bool) { + return cfg.ComponentConfigs.KubeProxy, cfg.ComponentConfigs.KubeProxy != nil + }, + SetToInternalConfig: func(obj runtime.Object, cfg *kubeadmapi.MasterConfiguration) bool { + kubeproxyConfig, ok := obj.(*kubeproxyconfig.KubeProxyConfiguration) + if ok { + cfg.ComponentConfigs.KubeProxy = kubeproxyConfig + } + return ok + }, + }, + KubeletConfigurationKind: { + MarshalGroupVersion: kubeletconfigv1beta1.SchemeGroupVersion, + AddToSchemeFuncs: []AddToSchemeFunc{kubeletconfig.AddToScheme, kubeletconfigv1beta1.AddToScheme}, + DefaulterFunc: DefaultKubeletConfiguration, + ValidateFunc: ValidateKubeletConfiguration, + EmptyValue: &kubeletconfig.KubeletConfiguration{}, + GetFromInternalConfig: func(cfg *kubeadmapi.MasterConfiguration) (runtime.Object, bool) { + return cfg.ComponentConfigs.Kubelet, cfg.ComponentConfigs.Kubelet != nil + }, + SetToInternalConfig: func(obj runtime.Object, cfg *kubeadmapi.MasterConfiguration) bool { + kubeletConfig, ok := obj.(*kubeletconfig.KubeletConfiguration) + if ok { + cfg.ComponentConfigs.Kubelet = kubeletConfig + } + return ok + }, + }, +} + +// AddToScheme adds all the known ComponentConfig API types referenced in the Registrations object to the scheme +func (rs *Registrations) AddToScheme(scheme *runtime.Scheme) error { + for _, registration := range *rs { + for _, addToSchemeFunc := range registration.AddToSchemeFuncs { + if err := addToSchemeFunc(scheme); err != nil { + return err + } + } + } + return nil +} + +// Default applies to the ComponentConfig defaults to the internal kubeadm API type +func (rs *Registrations) Default(internalcfg *kubeadmapi.MasterConfiguration) { + for _, registration := range *rs { + registration.DefaulterFunc(internalcfg) + } +} + +// Validate validates the ComponentConfig parts of the internal kubeadm API type +func (rs *Registrations) Validate(internalcfg *kubeadmapi.MasterConfiguration) field.ErrorList { + allErrs := field.ErrorList{} + for kind, registration := range *rs { + allErrs = append(allErrs, registration.ValidateFunc(internalcfg, field.NewPath(string(kind)))...) + } + return allErrs +} diff --git a/cmd/kubeadm/app/componentconfigs/scheme.go b/cmd/kubeadm/app/componentconfigs/scheme.go new file mode 100644 index 00000000000..edb545416c8 --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/scheme.go @@ -0,0 +1,41 @@ +/* +Copyright 2018 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 componentconfigs + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +// Scheme is the runtime.Scheme to which all supported kubeadm ComponentConfig API types are registered. +var Scheme = runtime.NewScheme() + +// Codecs provides access to encoding and decoding for the scheme. +var Codecs = serializer.NewCodecFactory(Scheme) + +func init() { + metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + AddToScheme(Scheme) +} + +// AddToScheme builds the kubeadm ComponentConfig scheme using all known ComponentConfig versions. +func AddToScheme(scheme *runtime.Scheme) { + utilruntime.Must(Known.AddToScheme(scheme)) +} diff --git a/cmd/kubeadm/app/componentconfigs/validation.go b/cmd/kubeadm/app/componentconfigs/validation.go new file mode 100644 index 00000000000..d15f6f8b9ec --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/validation.go @@ -0,0 +1,46 @@ +/* +Copyright 2018 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 componentconfigs + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeletvalidation "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/validation" + proxyvalidation "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/validation" +) + +// ValidateKubeProxyConfiguration validates proxy configuration and collects all encountered errors +func ValidateKubeProxyConfiguration(internalcfg *kubeadmapi.MasterConfiguration, _ *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if internalcfg.ComponentConfigs.KubeProxy == nil { + return allErrs + } + return proxyvalidation.Validate(internalcfg.ComponentConfigs.KubeProxy) +} + +// ValidateKubeletConfiguration validates kubelet configuration and collects all encountered errors +func ValidateKubeletConfiguration(internalcfg *kubeadmapi.MasterConfiguration, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if internalcfg.ComponentConfigs.Kubelet == nil { + return allErrs + } + + if err := kubeletvalidation.ValidateKubeletConfiguration(internalcfg.ComponentConfigs.Kubelet); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, "", err.Error())) + } + return allErrs +} diff --git a/cmd/kubeadm/app/componentconfigs/validation_test.go b/cmd/kubeadm/app/componentconfigs/validation_test.go new file mode 100644 index 00000000000..8e76614f4e4 --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/validation_test.go @@ -0,0 +1,351 @@ +/* +Copyright 2018 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 componentconfigs + +import ( + "strings" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" +) + +func TestValidateKubeProxyConfiguration(t *testing.T) { + var tests = []struct { + masterConfig *kubeadm.MasterConfiguration + msg string + expectErr bool + }{ + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + KubeProxy: &kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "192.168.59.103", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + expectErr: false, + }, + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + KubeProxy: &kubeproxyconfig.KubeProxyConfiguration{ + // only BindAddress is invalid + BindAddress: "10.10.12.11:2000", + HealthzBindAddress: "0.0.0.0:10256", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "not a valid textual representation of an IP address", + expectErr: true, + }, + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + KubeProxy: &kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + // only HealthzBindAddress is invalid + HealthzBindAddress: "0.0.0.0", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be IP:port", + expectErr: true, + }, + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + KubeProxy: &kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + // only MetricsBindAddress is invalid + MetricsBindAddress: "127.0.0.1", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be IP:port", + expectErr: true, + }, + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + KubeProxy: &kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + // only ClusterCIDR is invalid + ClusterCIDR: "192.168.59.0", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be a valid CIDR block (e.g. 10.100.0.0/16)", + expectErr: true, + }, + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + KubeProxy: &kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + // only UDPIdleTimeout is invalid + UDPIdleTimeout: metav1.Duration{Duration: -1 * time.Second}, + ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be greater than 0", + expectErr: true, + }, + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + KubeProxy: &kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "10.10.12.11", + HealthzBindAddress: "0.0.0.0:12345", + MetricsBindAddress: "127.0.0.1:10249", + ClusterCIDR: "192.168.59.0/24", + UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, + // only ConfigSyncPeriod is invalid + ConfigSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, + IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, + }, + IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ + SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, + }, + Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ + Max: utilpointer.Int32Ptr(2), + MaxPerCore: utilpointer.Int32Ptr(1), + Min: utilpointer.Int32Ptr(1), + TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, + TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, + }, + }, + }, + }, + msg: "must be greater than 0", + expectErr: true, + }, + } + for i, rt := range tests { + err := ValidateKubeProxyConfiguration(rt.masterConfig, nil).ToAggregate() + if (err != nil) != rt.expectErr { + t.Errorf("%d failed ValidateKubeProxyConfiguration: expected error %t, got error %t", i, rt.expectErr, err != nil) + } + if err != nil && !strings.Contains(err.Error(), rt.msg) { + t.Errorf("%d failed ValidateKubeProxyConfiguration: unexpected error: %v, expected: %s", i, err, rt.msg) + } + } +} + +func TestValidateKubeletConfiguration(t *testing.T) { + var tests = []struct { + masterConfig *kubeadm.MasterConfiguration + expectErr bool + }{ + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + Kubelet: &kubeletconfig.KubeletConfiguration{ + CgroupsPerQOS: true, + EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved"}, + SystemCgroups: "", + CgroupRoot: "", + EventBurst: 10, + EventRecordQPS: 5, + HealthzPort: 10248, + ImageGCHighThresholdPercent: 85, + ImageGCLowThresholdPercent: 80, + IPTablesDropBit: 15, + IPTablesMasqueradeBit: 14, + KubeAPIBurst: 10, + KubeAPIQPS: 5, + MaxOpenFiles: 1000000, + MaxPods: 110, + OOMScoreAdj: -999, + PodsPerCore: 100, + Port: 65535, + ReadOnlyPort: 0, + RegistryBurst: 10, + RegistryPullQPS: 5, + HairpinMode: "promiscuous-bridge", + }, + }, + }, + expectErr: false, + }, + { + masterConfig: &kubeadm.MasterConfiguration{ + ComponentConfigs: kubeadm.ComponentConfigs{ + Kubelet: &kubeletconfig.KubeletConfiguration{ + CgroupsPerQOS: false, + EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved", "illegal-key"}, + SystemCgroups: "/", + CgroupRoot: "", + EventBurst: -10, + EventRecordQPS: -10, + HealthzPort: -10, + ImageGCHighThresholdPercent: 101, + ImageGCLowThresholdPercent: 101, + IPTablesDropBit: -10, + IPTablesMasqueradeBit: -10, + KubeAPIBurst: -10, + KubeAPIQPS: -10, + MaxOpenFiles: -10, + MaxPods: -10, + OOMScoreAdj: -1001, + PodsPerCore: -10, + Port: 0, + ReadOnlyPort: -10, + RegistryBurst: -10, + RegistryPullQPS: -10, + }, + }, + }, + expectErr: true, + }, + } + for i, rt := range tests { + err := ValidateKubeletConfiguration(rt.masterConfig, nil).ToAggregate() + if (err != nil) != rt.expectErr { + t.Errorf("%d failed ValidateKubeletConfiguration: expected error %t, got error %t", i, rt.expectErr, err != nil) + } + } +}