From b881f19c8bea5fc79bcd6b59891042dc859e6fdf Mon Sep 17 00:00:00 2001 From: "Rostislav M. Georgiev" Date: Fri, 15 Nov 2019 17:11:08 +0200 Subject: [PATCH] kubeadm: Group centric component configs kubeadm's current implementation of component config support is "kind" centric. This has its downsides. Namely: - Kind names and numbers can change between config versions. Newer kinds can be ignored. Therefore, detection of a version change is considerably harder. - A component config can have only one kind that is managed by kubeadm. Thus a more appropriate way to identify component configs is required. Probably the best solution identified so far is a config group. A group name is unlikely to change between versions, while the kind names and structure can. Tracking component configs by group name allows us to: - Spot more easily config version changes and manage alternate versions. - Support more than one kind in a config group/version. - Abstract component configs by hiding their exact structure. Hence, this change rips off the old kind based support for component configs and replaces it with a group name based one. This also has the following extra benefits: - More tests were added. - kubeadm now errors out if an unsupported version of a known component group is used. Signed-off-by: Rostislav M. Georgiev --- cmd/kubeadm/app/apis/kubeadm/BUILD | 2 - cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go | 4 +- cmd/kubeadm/app/apis/kubeadm/types.go | 38 +- cmd/kubeadm/app/apis/kubeadm/validation/BUILD | 3 - .../app/apis/kubeadm/validation/validation.go | 2 +- .../kubeadm/validation/validation_test.go | 54 -- .../app/apis/kubeadm/zz_generated.deepcopy.go | 38 +- cmd/kubeadm/app/cmd/BUILD | 1 - cmd/kubeadm/app/cmd/config.go | 50 +- cmd/kubeadm/app/cmd/config_test.go | 13 +- cmd/kubeadm/app/cmd/phases/init/BUILD | 1 + cmd/kubeadm/app/cmd/phases/init/kubelet.go | 8 +- .../app/cmd/phases/init/uploadconfig.go | 2 +- cmd/kubeadm/app/componentconfigs/BUILD | 26 +- cmd/kubeadm/app/componentconfigs/config.go | 92 ---- .../app/componentconfigs/config_test.go | 147 ------ cmd/kubeadm/app/componentconfigs/configset.go | 204 ++++++++ .../app/componentconfigs/configset_test.go | 111 ++++ cmd/kubeadm/app/componentconfigs/defaults.go | 188 ------- cmd/kubeadm/app/componentconfigs/kubelet.go | 176 +++++++ .../app/componentconfigs/kubelet_test.go | 488 ++++++++++++++++++ cmd/kubeadm/app/componentconfigs/kubeproxy.go | 114 ++++ .../app/componentconfigs/kubeproxy_test.go | 440 ++++++++++++++++ .../app/componentconfigs/registrations.go | 158 ------ cmd/kubeadm/app/componentconfigs/scheme.go | 4 +- .../{validation.go => utils.go} | 13 +- cmd/kubeadm/app/phases/addons/proxy/BUILD | 3 - cmd/kubeadm/app/phases/addons/proxy/proxy.go | 7 +- .../app/phases/addons/proxy/proxy_test.go | 32 -- cmd/kubeadm/app/phases/kubelet/BUILD | 4 +- cmd/kubeadm/app/phases/kubelet/config.go | 22 +- cmd/kubeadm/app/phases/kubelet/config_test.go | 18 +- cmd/kubeadm/app/phases/upgrade/postupgrade.go | 2 +- .../app/phases/uploadconfig/uploadconfig.go | 2 +- .../phases/uploadconfig/uploadconfig_test.go | 14 +- cmd/kubeadm/app/util/config/BUILD | 1 + cmd/kubeadm/app/util/config/cluster.go | 25 +- cmd/kubeadm/app/util/config/cluster_test.go | 93 +--- .../app/util/config/initconfiguration.go | 43 +- .../app/util/config/joinconfiguration.go | 3 +- cmd/kubeadm/app/util/config/strict/BUILD | 1 - .../app/util/config/strict/strict_test.go | 13 +- cmd/kubeadm/app/util/marshal.go | 5 +- cmd/kubeadm/app/util/marshal_test.go | 7 +- cmd/kubeadm/test/cmd/init_test.go | 10 + .../init/current-component-config.yaml | 5 + .../testdata/init/old-component-config.yaml | 5 + 47 files changed, 1750 insertions(+), 942 deletions(-) delete mode 100644 cmd/kubeadm/app/componentconfigs/config.go delete mode 100644 cmd/kubeadm/app/componentconfigs/config_test.go create mode 100644 cmd/kubeadm/app/componentconfigs/configset.go create mode 100644 cmd/kubeadm/app/componentconfigs/configset_test.go delete mode 100644 cmd/kubeadm/app/componentconfigs/defaults.go create mode 100644 cmd/kubeadm/app/componentconfigs/kubelet.go create mode 100644 cmd/kubeadm/app/componentconfigs/kubelet_test.go create mode 100644 cmd/kubeadm/app/componentconfigs/kubeproxy.go create mode 100644 cmd/kubeadm/app/componentconfigs/kubeproxy_test.go delete mode 100644 cmd/kubeadm/app/componentconfigs/registrations.go rename cmd/kubeadm/app/componentconfigs/{validation.go => utils.go} (54%) create mode 100644 cmd/kubeadm/test/cmd/testdata/init/current-component-config.yaml create mode 100644 cmd/kubeadm/test/cmd/testdata/init/old-component-config.yaml diff --git a/cmd/kubeadm/app/apis/kubeadm/BUILD b/cmd/kubeadm/app/apis/kubeadm/BUILD index 5b96c168db8..92217b99d65 100644 --- a/cmd/kubeadm/app/apis/kubeadm/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/BUILD @@ -25,8 +25,6 @@ go_library( "//staging/src/k8s.io/cluster-bootstrap/token/api:go_default_library", "//staging/src/k8s.io/cluster-bootstrap/token/util:go_default_library", "//staging/src/k8s.io/cluster-bootstrap/util/secrets:go_default_library", - "//staging/src/k8s.io/kube-proxy/config/v1alpha1:go_default_library", - "//staging/src/k8s.io/kubelet/config/v1beta1:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", ], ) diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go index ffeabf041e2..c27bcdbca96 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go @@ -30,7 +30,7 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ fuzzInitConfiguration, fuzzClusterConfiguration, - fuzzComponentConfigs, + fuzzComponentConfigMap, fuzzDNS, fuzzNodeRegistration, fuzzLocalEtcd, @@ -116,7 +116,7 @@ func fuzzDNS(obj *kubeadm.DNS, c fuzz.Continue) { obj.Type = kubeadm.CoreDNS } -func fuzzComponentConfigs(obj *kubeadm.ComponentConfigs, c fuzz.Continue) { +func fuzzComponentConfigMap(obj *kubeadm.ComponentConfigMap, c fuzz.Continue) { // This is intentionally empty because component config does not exists in the public api // (empty mean all ComponentConfigs fields nil, and this is necessary for getting roundtrip passing) } diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index cb0656be571..4257dadfa7c 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -19,8 +19,8 @@ package kubeadm import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" - kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" + + "k8s.io/apimachinery/pkg/runtime/schema" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -62,9 +62,9 @@ type InitConfiguration struct { type ClusterConfiguration struct { metav1.TypeMeta - // ComponentConfigs holds internal ComponentConfig struct types known to kubeadm, should long-term only exist in the internal kubeadm API + // ComponentConfigs holds component configs known to kubeadm, should long-term only exist in the internal kubeadm API // +k8s:conversion-gen=false - ComponentConfigs ComponentConfigs + ComponentConfigs ComponentConfigMap // Etcd holds configuration for etcd. Etcd Etcd @@ -181,14 +181,6 @@ type ImageMeta struct { //TODO: evaluate if we need also a ImageName based on user feedbacks } -// ComponentConfigs holds known internal ComponentConfig types for other components -type ComponentConfigs struct { - // Kubelet holds the ComponentConfiguration for the kubelet - Kubelet *kubeletconfigv1beta1.KubeletConfiguration - // KubeProxy holds the ComponentConfiguration for the kube-proxy - KubeProxy *kubeproxyconfigv1alpha1.KubeProxyConfiguration -} - // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ClusterStatus contains the cluster status. The ClusterStatus will be stored in the kubeadm-config @@ -424,3 +416,25 @@ type HostPathMount struct { // PathType is the type of the HostPath. PathType v1.HostPathType } + +// DocumentMap is a convenient way to describe a map between a YAML document and its GVK type +// +k8s:deepcopy-gen=false +type DocumentMap map[schema.GroupVersionKind][]byte + +// ComponentConfig holds a known component config +type ComponentConfig interface { + // DeepCopy should create a new deep copy of the component config in place + DeepCopy() ComponentConfig + + // Marshal is marshalling the config into a YAML document returned as a byte slice + Marshal() ([]byte, error) + + // Unmarshal loads the config from a document map. No config in the document map is no error. + Unmarshal(docmap DocumentMap) error + + // Default patches the component config with kubeadm preferred defaults + Default(cfg *ClusterConfiguration, localAPIEndpoint *APIEndpoint) +} + +// ComponentConfigMap is a map between a group name (as in GVK group) and a ComponentConfig +type ComponentConfigMap map[string]ComponentConfig diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/BUILD b/cmd/kubeadm/app/apis/kubeadm/validation/BUILD index cee0dc87c64..f37cf38ebff 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/validation/BUILD @@ -32,12 +32,9 @@ go_test( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - "//staging/src/k8s.io/kube-proxy/config/v1alpha1:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", - "//vendor/k8s.io/utils/pointer:go_default_library", ], ) diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go index 70a0d84f46a..90129c69cf4 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation.go @@ -64,7 +64,7 @@ func ValidateClusterConfiguration(c *kubeadm.ClusterConfiguration) field.ErrorLi allErrs = append(allErrs, ValidateFeatureGates(c.FeatureGates, field.NewPath("featureGates"))...) allErrs = append(allErrs, ValidateHostPort(c.ControlPlaneEndpoint, field.NewPath("controlPlaneEndpoint"))...) allErrs = append(allErrs, ValidateEtcd(&c.Etcd, field.NewPath("etcd"))...) - allErrs = append(allErrs, componentconfigs.Known.Validate(c)...) + allErrs = append(allErrs, componentconfigs.Validate(c)...) return allErrs } diff --git a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go index 41efefebec6..0d5ff65aabb 100644 --- a/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go +++ b/cmd/kubeadm/app/apis/kubeadm/validation/validation_test.go @@ -20,16 +20,12 @@ import ( "io/ioutil" "os" "testing" - "time" "github.com/spf13/pflag" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" - kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" - utilpointer "k8s.io/utils/pointer" ) func TestValidateToken(t *testing.T) { @@ -448,31 +444,6 @@ func TestValidateInitConfiguration(t *testing.T) { DataDir: "/some/path", }, }, - ComponentConfigs: kubeadm.ComponentConfigs{ - KubeProxy: &kubeproxyconfigv1alpha1.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: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ - SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - }, - Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ - MaxPerCore: utilpointer.Int32Ptr(1), - Min: utilpointer.Int32Ptr(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, - }, - }, Networking: kubeadm.Networking{ ServiceSubnet: "10.96.0.1/12", DNSDomain: "cluster.local", @@ -494,31 +465,6 @@ func TestValidateInitConfiguration(t *testing.T) { DataDir: "/some/path", }, }, - ComponentConfigs: kubeadm.ComponentConfigs{ - KubeProxy: &kubeproxyconfigv1alpha1.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: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, - }, - IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ - SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, - }, - Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ - MaxPerCore: utilpointer.Int32Ptr(1), - Min: utilpointer.Int32Ptr(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, - }, - }, Networking: kubeadm.Networking{ ServiceSubnet: "2001:db8::1/98", DNSDomain: "cluster.local", diff --git a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go index 4e51357b9f8..76bb6958ea9 100644 --- a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go @@ -24,8 +24,6 @@ import ( corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - v1alpha1 "k8s.io/kube-proxy/config/v1alpha1" - v1beta1 "k8s.io/kubelet/config/v1beta1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -152,7 +150,13 @@ func (in *BootstrapTokenString) DeepCopy() *BootstrapTokenString { func (in *ClusterConfiguration) DeepCopyInto(out *ClusterConfiguration) { *out = *in out.TypeMeta = in.TypeMeta - in.ComponentConfigs.DeepCopyInto(&out.ComponentConfigs) + if in.ComponentConfigs != nil { + in, out := &in.ComponentConfigs, &out.ComponentConfigs + *out = make(ComponentConfigMap, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } in.Etcd.DeepCopyInto(&out.Etcd) out.Networking = in.Networking in.APIServer.DeepCopyInto(&out.APIServer) @@ -220,29 +224,25 @@ func (in *ClusterStatus) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentConfigs) DeepCopyInto(out *ComponentConfigs) { - *out = *in - if in.Kubelet != nil { - in, out := &in.Kubelet, &out.Kubelet - *out = new(v1beta1.KubeletConfiguration) - (*in).DeepCopyInto(*out) +func (in ComponentConfigMap) DeepCopyInto(out *ComponentConfigMap) { + { + in := &in + *out = make(ComponentConfigMap, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + return } - if in.KubeProxy != nil { - in, out := &in.KubeProxy, &out.KubeProxy - *out = new(v1alpha1.KubeProxyConfiguration) - (*in).DeepCopyInto(*out) - } - return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigs. -func (in *ComponentConfigs) DeepCopy() *ComponentConfigs { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigMap. +func (in ComponentConfigMap) DeepCopy() ComponentConfigMap { if in == nil { return nil } - out := new(ComponentConfigs) + out := new(ComponentConfigMap) in.DeepCopyInto(out) - return out + return *out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index d9ced02bed7..bec73d76b48 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -84,7 +84,6 @@ go_test( "//cmd/kubeadm/app/apis/output/scheme:go_default_library", "//cmd/kubeadm/app/apis/output/v1alpha1:go_default_library", "//cmd/kubeadm/app/cmd/options:go_default_library", - "//cmd/kubeadm/app/componentconfigs:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/features:go_default_library", "//cmd/kubeadm/app/util:go_default_library", diff --git a/cmd/kubeadm/app/cmd/config.go b/cmd/kubeadm/app/cmd/config.go index 903d0bec21c..ab502bb25df 100644 --- a/cmd/kubeadm/app/cmd/config.go +++ b/cmd/kubeadm/app/cmd/config.go @@ -116,7 +116,7 @@ func NewCmdConfigPrintJoinDefaults(out io.Writer) *cobra.Command { } func newCmdConfigPrintActionDefaults(out io.Writer, action string, configBytesProc func() ([]byte, error)) *cobra.Command { - componentConfigs := []string{} + kinds := []string{} cmd := &cobra.Command{ Use: fmt.Sprintf("%s-defaults", action), Short: fmt.Sprintf("Print default %s configuration, that can be used for 'kubeadm %s'", action, action), @@ -127,11 +127,15 @@ func newCmdConfigPrintActionDefaults(out io.Writer, action string, configBytesPr not perform the real computation for creating a token. `), action, action, placeholderToken), RunE: func(cmd *cobra.Command, args []string) error { - return runConfigPrintActionDefaults(out, componentConfigs, configBytesProc) + groups, err := mapLegacyKindsToGroups(kinds) + if err != nil { + return err + } + return runConfigPrintActionDefaults(out, groups, configBytesProc) }, } - cmd.Flags().StringSliceVar(&componentConfigs, "component-configs", componentConfigs, - fmt.Sprintf("A comma-separated list for component config API objects to print the default values for. Available values: %v. If this flag is not set, no component configs will be printed.", getSupportedComponentConfigAPIObjects())) + cmd.Flags().StringSliceVar(&kinds, "component-configs", kinds, + fmt.Sprintf("A comma-separated list for component config API objects to print the default values for. Available values: %v. If this flag is not set, no component configs will be printed.", getSupportedComponentConfigKinds())) return cmd } @@ -154,35 +158,49 @@ func runConfigPrintActionDefaults(out io.Writer, componentConfigs []string, conf return nil } -func getDefaultComponentConfigBytes(apiObject string) ([]byte, error) { - registration, ok := componentconfigs.Known[componentconfigs.RegistrationKind(apiObject)] - if !ok { - return []byte{}, errors.Errorf("--component-configs needs to contain some of %v", getSupportedComponentConfigAPIObjects()) - } - +func getDefaultComponentConfigBytes(group string) ([]byte, error) { defaultedInitConfig, err := getDefaultedInitConfig() if err != nil { return []byte{}, err } - realObj, ok := registration.GetFromInternalConfig(&defaultedInitConfig.ClusterConfiguration) + componentCfg, ok := defaultedInitConfig.ComponentConfigs[group] if !ok { - return []byte{}, errors.New("GetFromInternalConfig failed") + return []byte{}, errors.Errorf("cannot get defaulted config for component group %q", group) } - return registration.Marshal(realObj) + return componentCfg.Marshal() } -// getSupportedComponentConfigAPIObjects returns all currently supported component config API object names -func getSupportedComponentConfigAPIObjects() []string { +// legacyKindToGroupMap maps between the old API object types and the new way of specifying component configs (by group) +var legacyKindToGroupMap = map[string]string{ + "KubeletConfiguration": componentconfigs.KubeletGroup, + "KubeProxyConfiguration": componentconfigs.KubeProxyGroup, +} + +// getSupportedComponentConfigKinds returns all currently supported component config API object names +func getSupportedComponentConfigKinds() []string { objects := []string{} - for componentType := range componentconfigs.Known { + for componentType := range legacyKindToGroupMap { objects = append(objects, string(componentType)) } sort.Strings(objects) return objects } +func mapLegacyKindsToGroups(kinds []string) ([]string, error) { + groups := []string{} + for _, kind := range kinds { + group, ok := legacyKindToGroupMap[kind] + if ok { + groups = append(groups, group) + } else { + return nil, errors.Errorf("--component-configs needs to contain some of %v", getSupportedComponentConfigKinds()) + } + } + return groups, nil +} + func getDefaultedInitConfig() (*kubeadmapi.InitConfiguration, error) { initCfg := &kubeadmapiv1beta2.InitConfiguration{ LocalAPIEndpoint: kubeadmapiv1beta2.APIEndpoint{AdvertiseAddress: "1.2.3.4"}, diff --git a/cmd/kubeadm/app/cmd/config_test.go b/cmd/kubeadm/app/cmd/config_test.go index ebfa02e49ea..07228692ef6 100644 --- a/cmd/kubeadm/app/cmd/config_test.go +++ b/cmd/kubeadm/app/cmd/config_test.go @@ -31,7 +31,6 @@ import ( "github.com/lithammer/dedent" "github.com/spf13/cobra" kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" - "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" @@ -299,7 +298,7 @@ func TestNewCmdConfigPrintActionDefaults(t *testing.T) { expectedKinds: []string{ constants.ClusterConfigurationKind, constants.InitConfigurationKind, - string(componentconfigs.KubeProxyConfigurationKind), + "KubeProxyConfiguration", }, componentConfigs: "KubeProxyConfiguration", cmdProc: NewCmdConfigPrintInitDefaults, @@ -309,8 +308,8 @@ func TestNewCmdConfigPrintActionDefaults(t *testing.T) { expectedKinds: []string{ constants.ClusterConfigurationKind, constants.InitConfigurationKind, - string(componentconfigs.KubeProxyConfigurationKind), - string(componentconfigs.KubeletConfigurationKind), + "KubeProxyConfiguration", + "KubeletConfiguration", }, componentConfigs: "KubeProxyConfiguration,KubeletConfiguration", cmdProc: NewCmdConfigPrintInitDefaults, @@ -326,7 +325,7 @@ func TestNewCmdConfigPrintActionDefaults(t *testing.T) { name: "JoinConfiguration: KubeProxyConfiguration", expectedKinds: []string{ constants.JoinConfigurationKind, - string(componentconfigs.KubeProxyConfigurationKind), + "KubeProxyConfiguration", }, componentConfigs: "KubeProxyConfiguration", cmdProc: NewCmdConfigPrintJoinDefaults, @@ -335,8 +334,8 @@ func TestNewCmdConfigPrintActionDefaults(t *testing.T) { name: "JoinConfiguration: KubeProxyConfiguration and KubeletConfiguration", expectedKinds: []string{ constants.JoinConfigurationKind, - string(componentconfigs.KubeProxyConfigurationKind), - string(componentconfigs.KubeletConfigurationKind), + "KubeProxyConfiguration", + "KubeletConfiguration", }, componentConfigs: "KubeProxyConfiguration,KubeletConfiguration", cmdProc: NewCmdConfigPrintJoinDefaults, diff --git a/cmd/kubeadm/app/cmd/phases/init/BUILD b/cmd/kubeadm/app/cmd/phases/init/BUILD index dff5114ad22..d004f6a32ef 100644 --- a/cmd/kubeadm/app/cmd/phases/init/BUILD +++ b/cmd/kubeadm/app/cmd/phases/init/BUILD @@ -27,6 +27,7 @@ go_library( "//cmd/kubeadm/app/cmd/options:go_default_library", "//cmd/kubeadm/app/cmd/phases/workflow:go_default_library", "//cmd/kubeadm/app/cmd/util:go_default_library", + "//cmd/kubeadm/app/componentconfigs:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/phases/addons/dns:go_default_library", "//cmd/kubeadm/app/phases/addons/proxy:go_default_library", diff --git a/cmd/kubeadm/app/cmd/phases/init/kubelet.go b/cmd/kubeadm/app/cmd/phases/init/kubelet.go index 207e8587c4c..a96d8bae982 100644 --- a/cmd/kubeadm/app/cmd/phases/init/kubelet.go +++ b/cmd/kubeadm/app/cmd/phases/init/kubelet.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" + "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" ) @@ -71,8 +72,13 @@ func runKubeletStart(c workflow.RunData) error { return errors.Wrap(err, "error writing a dynamic environment file for the kubelet") } + kubeletCfg, ok := data.Cfg().ComponentConfigs[componentconfigs.KubeletGroup] + if !ok { + return errors.New("no kubelet component config found in the active component config set") + } + // Write the kubelet configuration file to disk. - if err := kubeletphase.WriteConfigToDisk(data.Cfg().ComponentConfigs.Kubelet, data.KubeletDir()); err != nil { + if err := kubeletphase.WriteConfigToDisk(kubeletCfg, data.KubeletDir()); err != nil { return errors.Wrap(err, "error writing kubelet configuration to disk") } diff --git a/cmd/kubeadm/app/cmd/phases/init/uploadconfig.go b/cmd/kubeadm/app/cmd/phases/init/uploadconfig.go index 4f693f83e84..dc0e746dd5f 100644 --- a/cmd/kubeadm/app/cmd/phases/init/uploadconfig.go +++ b/cmd/kubeadm/app/cmd/phases/init/uploadconfig.go @@ -120,7 +120,7 @@ func runUploadKubeletConfig(c workflow.RunData) error { } klog.V(1).Infoln("[upload-config] Uploading the kubelet component config to a ConfigMap") - if err = kubeletphase.CreateConfigMap(cfg.ClusterConfiguration.ComponentConfigs.Kubelet, cfg.KubernetesVersion, client); err != nil { + if err = kubeletphase.CreateConfigMap(&cfg.ClusterConfiguration, client); err != nil { return errors.Wrap(err, "error creating kubelet configuration ConfigMap") } diff --git a/cmd/kubeadm/app/componentconfigs/BUILD b/cmd/kubeadm/app/componentconfigs/BUILD index 4959a1f6130..5039010361c 100644 --- a/cmd/kubeadm/app/componentconfigs/BUILD +++ b/cmd/kubeadm/app/componentconfigs/BUILD @@ -3,11 +3,11 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "config.go", - "defaults.go", - "registrations.go", + "configset.go", + "kubelet.go", + "kubeproxy.go", "scheme.go", - "validation.go", + "utils.go", ], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs", visibility = ["//visibility:public"], @@ -37,16 +37,28 @@ go_library( go_test( name = "go_default_test", - srcs = ["config_test.go"], + srcs = [ + "configset_test.go", + "kubelet_test.go", + "kubeproxy_test.go", + ], embed = [":go_default_library"], deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", - "//cmd/kubeadm/app/util/apiclient:go_default_library", + "//cmd/kubeadm/app/features:go_default_library", + "//cmd/kubeadm/app/util:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library", - "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", + "//staging/src/k8s.io/component-base/config/v1alpha1:go_default_library", + "//staging/src/k8s.io/kube-proxy/config/v1alpha1:go_default_library", + "//staging/src/k8s.io/kubelet/config/v1beta1:go_default_library", + "//vendor/github.com/lithammer/dedent:go_default_library", + "//vendor/k8s.io/utils/pointer:go_default_library", ], ) diff --git a/cmd/kubeadm/app/componentconfigs/config.go b/cmd/kubeadm/app/componentconfigs/config.go deleted file mode 100644 index b6c81692a6a..00000000000 --- a/cmd/kubeadm/app/componentconfigs/config.go +++ /dev/null @@ -1,92 +0,0 @@ -/* -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 ( - "github.com/pkg/errors" - "k8s.io/klog" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/version" - clientset "k8s.io/client-go/kubernetes" - kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" - kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" - kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" -) - -// GetFromKubeletConfigMap returns the pointer to the ComponentConfig API object read from the kubelet-config-version -// ConfigMap map stored in the cluster -func GetFromKubeletConfigMap(client clientset.Interface, version *version.Version) (runtime.Object, error) { - - // Read the ConfigMap from the cluster based on what version the kubelet is - configMapName := kubeadmconstants.GetKubeletConfigMapName(version) - kubeletCfg, err := apiclient.GetConfigMapWithRetry(client, metav1.NamespaceSystem, configMapName) - if err != nil { - return nil, err - } - - kubeletConfigData, ok := kubeletCfg.Data[kubeadmconstants.KubeletBaseConfigurationConfigMapKey] - if !ok { - return nil, errors.Errorf("unexpected error when reading %s ConfigMap: %s key value pair missing", - configMapName, kubeadmconstants.KubeletBaseConfigurationConfigMapKey) - } - - // Decodes the kubeletConfigData into the internal component config - obj := &kubeletconfigv1beta1.KubeletConfiguration{} - err = unmarshalObject(obj, []byte(kubeletConfigData)) - if err != nil { - return nil, err - } - - return obj, nil -} - -// GetFromKubeProxyConfigMap returns the pointer to the ComponentConfig API object read from the kube-proxy -// ConfigMap map stored in the cluster -func GetFromKubeProxyConfigMap(client clientset.Interface, version *version.Version) (runtime.Object, error) { - - // Read the ConfigMap from the cluster - kubeproxyCfg, err := apiclient.GetConfigMapWithRetry(client, metav1.NamespaceSystem, kubeadmconstants.KubeProxyConfigMap) - if err != nil { - // The Kube-Proxy config map may be non-existent, because the user has decided to manage it by themselves - // or to use other proxy solution. It may also be forbidden - if the kube-proxy phase was skipped we have neither - // the config map, nor the RBAC rules allowing join access to it. - if apierrors.IsNotFound(err) || apierrors.IsForbidden(err) { - klog.Warningf("Warning: No kube-proxy config is loaded. Continuing without it: %v", err) - return nil, nil - } - return nil, err - } - - kubeproxyConfigData, ok := kubeproxyCfg.Data[kubeadmconstants.KubeProxyConfigMapKey] - if !ok { - return nil, errors.Errorf("unexpected error when reading %s ConfigMap: %s key value pair missing", - kubeadmconstants.KubeProxyConfigMap, kubeadmconstants.KubeProxyConfigMapKey) - } - - // Decodes the Config map dat into the internal component config - obj := &kubeproxyconfigv1alpha1.KubeProxyConfiguration{} - err = unmarshalObject(obj, []byte(kubeproxyConfigData)) - if err != nil { - return nil, err - } - - return obj, nil -} diff --git a/cmd/kubeadm/app/componentconfigs/config_test.go b/cmd/kubeadm/app/componentconfigs/config_test.go deleted file mode 100644 index 8d0dada420a..00000000000 --- a/cmd/kubeadm/app/componentconfigs/config_test.go +++ /dev/null @@ -1,147 +0,0 @@ -/* -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 ( - "testing" - - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/version" - clientset "k8s.io/client-go/kubernetes" - clientsetfake "k8s.io/client-go/kubernetes/fake" - kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" -) - -var cfgFiles = map[string][]byte{ - "Kube-proxy_componentconfig": []byte(` -apiVersion: kubeproxy.config.k8s.io/v1alpha1 -kind: KubeProxyConfiguration -`), - "Kubelet_componentconfig": []byte(` -apiVersion: kubelet.config.k8s.io/v1beta1 -kind: KubeletConfiguration -`), -} - -func TestGetFromConfigMap(t *testing.T) { - k8sVersion := version.MustParseGeneric(kubeadmconstants.CurrentKubernetesVersion.String()) - - var tests = []struct { - name string - component RegistrationKind - configMap *fakeConfigMap - expectedError bool - expectedNil bool - }{ - { - name: "valid kube-proxy", - component: KubeProxyConfigurationKind, - configMap: &fakeConfigMap{ - name: kubeadmconstants.KubeProxyConfigMap, - data: map[string]string{ - kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]), - }, - }, - }, - { - name: "valid kube-proxy - missing ConfigMap", - component: KubeProxyConfigurationKind, - configMap: nil, - expectedNil: true, - }, - { - name: "invalid kube-proxy - missing key", - component: KubeProxyConfigurationKind, - configMap: &fakeConfigMap{ - name: kubeadmconstants.KubeProxyConfigMap, - data: map[string]string{}, - }, - expectedError: true, - }, - { - name: "valid kubelet", - component: KubeletConfigurationKind, - configMap: &fakeConfigMap{ - name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), - data: map[string]string{ - kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]), - }, - }, - }, - { - name: "invalid kubelet - missing ConfigMap", - component: KubeletConfigurationKind, - configMap: nil, - expectedError: true, - }, - { - name: "invalid kubelet - missing key", - component: KubeletConfigurationKind, - configMap: &fakeConfigMap{ - name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), - data: map[string]string{}, - }, - expectedError: true, - }, - } - - for _, rt := range tests { - t.Run(rt.name, func(t2 *testing.T) { - client := clientsetfake.NewSimpleClientset() - - if rt.configMap != nil { - err := rt.configMap.create(client) - if err != nil { - t.Errorf("unexpected create ConfigMap %s", rt.configMap.name) - return - } - } - - registration := Known[rt.component] - - obj, err := registration.GetFromConfigMap(client, k8sVersion) - if rt.expectedError != (err != nil) { - t.Errorf("unexpected return err from GetFromConfigMap: %v", err) - return - } - if rt.expectedError { - return - } - - if rt.expectedNil != (obj == nil) { - t.Error("unexpected return value") - } - }) - } -} - -type fakeConfigMap struct { - name string - data map[string]string -} - -func (c *fakeConfigMap) create(client clientset.Interface) error { - return apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: c.name, - Namespace: metav1.NamespaceSystem, - }, - Data: c.data, - }) -} diff --git a/cmd/kubeadm/app/componentconfigs/configset.go b/cmd/kubeadm/app/componentconfigs/configset.go new file mode 100644 index 00000000000..5cdbbfc1277 --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/configset.go @@ -0,0 +1,204 @@ +/* +Copyright 2019 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 ( + "sort" + + "github.com/pkg/errors" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/klog" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" +) + +// handler is a package internal type that handles component config factory and common functionality. +// Every component config group should have exactly one static instance of handler. +type handler struct { + // GroupVersion holds this handler's group name and preferred version + GroupVersion schema.GroupVersion + + // AddToScheme points to a func that should add the GV types to a schema + AddToScheme func(*runtime.Scheme) error + + // CreateEmpty returns an empty kubeadmapi.ComponentConfig (not even defaulted) + CreateEmpty func() kubeadmapi.ComponentConfig + + // fromCluster should load the component config from a config map on the cluster. + // Don't use this directly! Use FromCluster instead! + fromCluster func(*handler, clientset.Interface, *kubeadmapi.ClusterConfiguration) (kubeadmapi.ComponentConfig, error) +} + +// FromDocumentMap looks in the document map for documents with this handler's group. +// If such are found a new component config is instantiated and the documents are loaded into it. +// No error is returned if no documents are found. +func (h *handler) FromDocumentMap(docmap kubeadmapi.DocumentMap) (kubeadmapi.ComponentConfig, error) { + for gvk := range docmap { + if gvk.Group == h.GroupVersion.Group { + cfg := h.CreateEmpty() + if err := cfg.Unmarshal(docmap); err != nil { + return nil, err + } + return cfg, nil + } + } + return nil, nil +} + +// fromConfigMap is an utility function, which will load the value of a key of a config map and use h.FromDocumentMap() to perform the parsing +// This is an utility func. Used by the component config support implementations. Don't use it outside of that context. +func (h *handler) fromConfigMap(client clientset.Interface, cmName, cmKey string, mustExist bool) (kubeadmapi.ComponentConfig, error) { + configMap, err := apiclient.GetConfigMapWithRetry(client, metav1.NamespaceSystem, cmName) + if err != nil { + if !mustExist && (apierrors.IsNotFound(err) || apierrors.IsForbidden(err)) { + klog.Warningf("Warning: No %s config is loaded. Continuing without it: %v", h.GroupVersion, err) + return nil, nil + } + return nil, err + } + + configData, ok := configMap.Data[cmKey] + if !ok { + return nil, errors.Errorf("unexpected error when reading %s ConfigMap: %s key value pair missing", cmName, cmKey) + } + + gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(configData)) + if err != nil { + return nil, err + } + + return h.FromDocumentMap(gvkmap) +} + +// FromCluster loads a component from a config map in the cluster +func (h *handler) FromCluster(clientset clientset.Interface, clusterCfg *kubeadmapi.ClusterConfiguration) (kubeadmapi.ComponentConfig, error) { + return h.fromCluster(h, clientset, clusterCfg) +} + +// Marshal is an utility function, used by the component config support implementations to marshal a runtime.Object to YAML with the +// correct group and version +func (h *handler) Marshal(object runtime.Object) ([]byte, error) { + return kubeadmutil.MarshalToYamlForCodecs(object, h.GroupVersion, Codecs) +} + +// Unmarshal attempts to unmarshal a runtime.Object from a document map. If no object is found, no error is returned. +// If a matching group is found, but no matching version an error is returned indicating that users should do manual conversion. +func (h *handler) Unmarshal(from kubeadmapi.DocumentMap, into runtime.Object) error { + for gvk, yaml := range from { + // If this is a different group, we ignore it + if gvk.Group != h.GroupVersion.Group { + continue + } + + // If this is the correct group, but different version, we return an error + if gvk.Version != h.GroupVersion.Version { + // TODO: Replace this with a special error type and make UX better around it + return errors.Errorf("unexpected apiVersion %q, you may have to do manual conversion to %q and execute kubeadm again", gvk.GroupVersion(), h.GroupVersion) + } + + // As long as we support only component configs with a single kind, this is allowed + return runtime.DecodeInto(Codecs.UniversalDecoder(), yaml, into) + } + + return nil +} + +// known holds the known component config handlers. Add new component configs here. +var known = []*handler{ + &kubeProxyHandler, + &kubeletHandler, +} + +// ensureInitializedComponentConfigs is an utility func to initialize the ComponentConfigMap in ClusterConfiguration prior to possible writes to it +func ensureInitializedComponentConfigs(clusterCfg *kubeadmapi.ClusterConfiguration) { + if clusterCfg.ComponentConfigs == nil { + clusterCfg.ComponentConfigs = kubeadmapi.ComponentConfigMap{} + } +} + +// Default sets up defaulted component configs in the supplied ClusterConfiguration +func Default(clusterCfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint *kubeadmapi.APIEndpoint) { + ensureInitializedComponentConfigs(clusterCfg) + + for _, handler := range known { + // If the component config exists, simply default it. Otherwise, create it before defaulting. + group := handler.GroupVersion.Group + if componentCfg, ok := clusterCfg.ComponentConfigs[group]; ok { + componentCfg.Default(clusterCfg, localAPIEndpoint) + } else { + componentCfg := handler.CreateEmpty() + componentCfg.Default(clusterCfg, localAPIEndpoint) + clusterCfg.ComponentConfigs[group] = componentCfg + } + } +} + +// FetchFromCluster attempts to fetch all known component configs from their config maps and store them in the supplied ClusterConfiguration +func FetchFromCluster(clusterCfg *kubeadmapi.ClusterConfiguration, client clientset.Interface) error { + ensureInitializedComponentConfigs(clusterCfg) + + for _, handler := range known { + componentCfg, err := handler.FromCluster(client, clusterCfg) + if err != nil { + return err + } + + if componentCfg != nil { + clusterCfg.ComponentConfigs[handler.GroupVersion.Group] = componentCfg + } + } + + return nil +} + +// FetchFromDocumentMap attempts to load all known component configs from a document map into the supplied ClusterConfiguration +func FetchFromDocumentMap(clusterCfg *kubeadmapi.ClusterConfiguration, docmap kubeadmapi.DocumentMap) error { + ensureInitializedComponentConfigs(clusterCfg) + + for _, handler := range known { + componentCfg, err := handler.FromDocumentMap(docmap) + if err != nil { + return err + } + + if componentCfg != nil { + clusterCfg.ComponentConfigs[handler.GroupVersion.Group] = componentCfg + } + } + + return nil +} + +// Validate is a placeholder for performing a validation on an already loaded component configs in a ClusterConfiguration +// Currently it prints a warning that no validation was performed +func Validate(clusterCfg *kubeadmapi.ClusterConfiguration) field.ErrorList { + groups := []string{} + for group := range clusterCfg.ComponentConfigs { + groups = append(groups, group) + } + sort.Strings(groups) // The sort is needed to make the output predictable + klog.Warningf("WARNING: kubeadm cannot validate component configs for API groups %v", groups) + return field.ErrorList{} +} diff --git a/cmd/kubeadm/app/componentconfigs/configset_test.go b/cmd/kubeadm/app/componentconfigs/configset_test.go new file mode 100644 index 00000000000..3b0cf83d622 --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/configset_test.go @@ -0,0 +1,111 @@ +/* +Copyright 2019 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 ( + "testing" + + "github.com/lithammer/dedent" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/version" + clientsetfake "k8s.io/client-go/kubernetes/fake" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" +) + +func TestDefault(t *testing.T) { + clusterCfg := &kubeadmapi.ClusterConfiguration{} + localAPIEndpoint := &kubeadmapi.APIEndpoint{} + + Default(clusterCfg, localAPIEndpoint) + + if len(clusterCfg.ComponentConfigs) != len(known) { + t.Errorf("missmatch between supported and defaulted type numbers:\n\tgot: %d\n\texpected: %d", len(clusterCfg.ComponentConfigs), len(known)) + } +} + +func TestFromCluster(t *testing.T) { + clusterCfg := &kubeadmapi.ClusterConfiguration{ + KubernetesVersion: constants.CurrentKubernetesVersion.String(), + } + + k8sVersion := version.MustParseGeneric(clusterCfg.KubernetesVersion) + + objects := []runtime.Object{ + &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.KubeProxyConfigMap, + Namespace: metav1.NamespaceSystem, + }, + Data: map[string]string{ + constants.KubeProxyConfigMapKey: dedent.Dedent(` + apiVersion: kubeproxy.config.k8s.io/v1alpha1 + kind: KubeProxyConfiguration + `), + }, + }, + &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.GetKubeletConfigMapName(k8sVersion), + Namespace: metav1.NamespaceSystem, + }, + Data: map[string]string{ + constants.KubeletBaseConfigurationConfigMapKey: dedent.Dedent(` + apiVersion: kubelet.config.k8s.io/v1beta1 + kind: KubeletConfiguration + `), + }, + }, + } + client := clientsetfake.NewSimpleClientset(objects...) + + if err := FetchFromCluster(clusterCfg, client); err != nil { + t.Fatalf("FetchFromCluster failed: %v", err) + } + + if len(clusterCfg.ComponentConfigs) != len(objects) { + t.Fatalf("missmatch between supplied and loaded type numbers:\n\tgot: %d\n\texpected: %d", len(clusterCfg.ComponentConfigs), len(objects)) + } +} + +func TestFetchFromDocumentMap(t *testing.T) { + test := dedent.Dedent(` + apiVersion: kubeproxy.config.k8s.io/v1alpha1 + kind: KubeProxyConfiguration + --- + apiVersion: kubelet.config.k8s.io/v1beta1 + kind: KubeletConfiguration + `) + gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(test)) + if err != nil { + t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err) + } + + clusterCfg := &kubeadmapi.ClusterConfiguration{} + if err = FetchFromDocumentMap(clusterCfg, gvkmap); err != nil { + t.Fatalf("FetchFromDocumentMap failed: %v", err) + } + + if len(clusterCfg.ComponentConfigs) != len(gvkmap) { + t.Fatalf("missmatch between supplied and loaded type numbers:\n\tgot: %d\n\texpected: %d", len(clusterCfg.ComponentConfigs), len(gvkmap)) + } +} diff --git a/cmd/kubeadm/app/componentconfigs/defaults.go b/cmd/kubeadm/app/componentconfigs/defaults.go deleted file mode 100644 index 4e289ef676b..00000000000 --- a/cmd/kubeadm/app/componentconfigs/defaults.go +++ /dev/null @@ -1,188 +0,0 @@ -/* -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 ( - "path/filepath" - - "k8s.io/klog" - - kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" - kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" - "k8s.io/kubernetes/cmd/kubeadm/app/constants" - "k8s.io/kubernetes/cmd/kubeadm/app/features" - utilpointer "k8s.io/utils/pointer" -) - -const ( - // kubeproxyKubeConfigFileName defines the file name for the kube-proxy's kubeconfig file - kubeproxyKubeConfigFileName = "/var/lib/kube-proxy/kubeconfig.conf" - - // kubeletReadOnlyPort specifies the default insecure http server port - // 0 will disable insecure http server. - kubeletReadOnlyPort int32 = 0 - - // kubeletRotateCertificates specifies the default value to enable certificate rotation - kubeletRotateCertificates = true - - // kubeletAuthenticationAnonymousEnabled specifies the default value to disable anonymous access - kubeletAuthenticationAnonymousEnabled = false - - // kubeletAuthorizationMode specifies the default authorization mode - kubeletAuthorizationMode = kubeletconfigv1beta1.KubeletAuthorizationModeWebhook - - // kubeletAuthenticationWebhookEnabled set the default value to enable authentication webhook - kubeletAuthenticationWebhookEnabled = true - - // kubeletHealthzBindAddress specifies the default healthz bind address - kubeletHealthzBindAddress = "127.0.0.1" -) - -// DefaultKubeProxyConfiguration assigns default values for the kube-proxy ComponentConfig -func DefaultKubeProxyConfiguration(internalcfg *kubeadmapi.ClusterConfiguration) { - kind := "KubeProxyConfiguration" - - if internalcfg.ComponentConfigs.KubeProxy == nil { - internalcfg.ComponentConfigs.KubeProxy = &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ - FeatureGates: map[string]bool{}, - } - } - // The below code is necessary because while KubeProxy may be defined, the user may not - // have defined any feature-gates, thus FeatureGates will be nil and the later insertion - // of any feature-gates (e.g. IPv6DualStack) will cause a panic. - if internalcfg.ComponentConfigs.KubeProxy.FeatureGates == nil { - internalcfg.ComponentConfigs.KubeProxy.FeatureGates = map[string]bool{} - } - - externalproxycfg := internalcfg.ComponentConfigs.KubeProxy - - if externalproxycfg.ClusterCIDR == "" && internalcfg.Networking.PodSubnet != "" { - externalproxycfg.ClusterCIDR = internalcfg.Networking.PodSubnet - } else if internalcfg.Networking.PodSubnet != "" && externalproxycfg.ClusterCIDR != internalcfg.Networking.PodSubnet { - warnDefaultComponentConfigValue(kind, "clusterCIDR", internalcfg.Networking.PodSubnet, externalproxycfg.ClusterCIDR) - } - - if externalproxycfg.ClientConnection.Kubeconfig == "" { - externalproxycfg.ClientConnection.Kubeconfig = kubeproxyKubeConfigFileName - } else if externalproxycfg.ClientConnection.Kubeconfig != kubeproxyKubeConfigFileName { - warnDefaultComponentConfigValue(kind, "clientConnection.kubeconfig", kubeproxyKubeConfigFileName, externalproxycfg.ClientConnection.Kubeconfig) - } - - // TODO: The following code should be remvoved after dual-stack is GA. - // Note: The user still retains the ability to explicitly set feature-gates and that value will overwrite this base value. - if enabled, present := internalcfg.FeatureGates[features.IPv6DualStack]; present { - externalproxycfg.FeatureGates[features.IPv6DualStack] = enabled - } -} - -// DefaultKubeletConfiguration assigns default values for the kubelet ComponentConfig -func DefaultKubeletConfiguration(internalcfg *kubeadmapi.ClusterConfiguration) { - kind := "KubeletConfiguration" - - if internalcfg.ComponentConfigs.Kubelet == nil { - internalcfg.ComponentConfigs.Kubelet = &kubeletconfigv1beta1.KubeletConfiguration{ - FeatureGates: map[string]bool{}, - } - } - - externalkubeletcfg := internalcfg.ComponentConfigs.Kubelet - - if externalkubeletcfg.StaticPodPath == "" { - externalkubeletcfg.StaticPodPath = kubeadmapiv1beta2.DefaultManifestsDir - } else if externalkubeletcfg.StaticPodPath != kubeadmapiv1beta2.DefaultManifestsDir { - warnDefaultComponentConfigValue(kind, "staticPodPath", kubeadmapiv1beta2.DefaultManifestsDir, externalkubeletcfg.StaticPodPath) - } - - clusterDNS := "" - dnsIP, err := constants.GetDNSIP(internalcfg.Networking.ServiceSubnet, features.Enabled(internalcfg.FeatureGates, features.IPv6DualStack)) - if err != nil { - clusterDNS = kubeadmapiv1beta2.DefaultClusterDNSIP - } else { - clusterDNS = dnsIP.String() - } - - if externalkubeletcfg.ClusterDNS == nil { - externalkubeletcfg.ClusterDNS = []string{clusterDNS} - } else if len(externalkubeletcfg.ClusterDNS) != 1 || externalkubeletcfg.ClusterDNS[0] != clusterDNS { - warnDefaultComponentConfigValue(kind, "clusterDNS", []string{clusterDNS}, externalkubeletcfg.ClusterDNS) - } - - if externalkubeletcfg.ClusterDomain == "" { - externalkubeletcfg.ClusterDomain = internalcfg.Networking.DNSDomain - } else if internalcfg.Networking.DNSDomain != "" && externalkubeletcfg.ClusterDomain != internalcfg.Networking.DNSDomain { - warnDefaultComponentConfigValue(kind, "clusterDomain", internalcfg.Networking.DNSDomain, externalkubeletcfg.ClusterDomain) - } - - // Require all clients to the kubelet API to have client certs signed by the cluster CA - clientCAFile := filepath.Join(internalcfg.CertificatesDir, constants.CACertName) - if externalkubeletcfg.Authentication.X509.ClientCAFile == "" { - externalkubeletcfg.Authentication.X509.ClientCAFile = clientCAFile - } else if externalkubeletcfg.Authentication.X509.ClientCAFile != clientCAFile { - warnDefaultComponentConfigValue(kind, "authentication.x509.clientCAFile", clientCAFile, externalkubeletcfg.Authentication.X509.ClientCAFile) - } - - if externalkubeletcfg.Authentication.Anonymous.Enabled == nil { - externalkubeletcfg.Authentication.Anonymous.Enabled = utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled) - } else if *externalkubeletcfg.Authentication.Anonymous.Enabled != kubeletAuthenticationAnonymousEnabled { - warnDefaultComponentConfigValue(kind, "authentication.anonymous.enabled", kubeletAuthenticationAnonymousEnabled, *externalkubeletcfg.Authentication.Anonymous.Enabled) - } - - // 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 - if externalkubeletcfg.Authorization.Mode == "" { - externalkubeletcfg.Authorization.Mode = kubeletAuthorizationMode - } else if externalkubeletcfg.Authorization.Mode != kubeletAuthorizationMode { - warnDefaultComponentConfigValue(kind, "authorization.mode", kubeletAuthorizationMode, externalkubeletcfg.Authorization.Mode) - } - - // Let clients using other authentication methods like ServiceAccount tokens also access the kubelet API - if externalkubeletcfg.Authentication.Webhook.Enabled == nil { - externalkubeletcfg.Authentication.Webhook.Enabled = utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled) - } else if *externalkubeletcfg.Authentication.Webhook.Enabled != kubeletAuthenticationWebhookEnabled { - warnDefaultComponentConfigValue(kind, "authentication.webhook.enabled", kubeletAuthenticationWebhookEnabled, *externalkubeletcfg.Authentication.Webhook.Enabled) - } - - // Serve a /healthz webserver on localhost:10248 that kubeadm can talk to - if externalkubeletcfg.HealthzBindAddress == "" { - externalkubeletcfg.HealthzBindAddress = kubeletHealthzBindAddress - } else if externalkubeletcfg.HealthzBindAddress != kubeletHealthzBindAddress { - warnDefaultComponentConfigValue(kind, "healthzBindAddress", kubeletHealthzBindAddress, externalkubeletcfg.HealthzBindAddress) - } - - if externalkubeletcfg.HealthzPort == nil { - externalkubeletcfg.HealthzPort = utilpointer.Int32Ptr(constants.KubeletHealthzPort) - } else if *externalkubeletcfg.HealthzPort != constants.KubeletHealthzPort { - warnDefaultComponentConfigValue(kind, "healthzPort", constants.KubeletHealthzPort, *externalkubeletcfg.HealthzPort) - } - - if externalkubeletcfg.ReadOnlyPort != kubeletReadOnlyPort { - warnDefaultComponentConfigValue(kind, "readOnlyPort", kubeletReadOnlyPort, externalkubeletcfg.ReadOnlyPort) - } - - // We cannot show a warning for RotateCertificates==false and we must hardcode it to true. - // There is no way to determine if the user has set this or not, given the field is a non-pointer. - externalkubeletcfg.RotateCertificates = kubeletRotateCertificates -} - -// warnDefaultComponentConfigValue prints a warning if the user modified a field in a certain -// CompomentConfig from the default recommended value in kubeadm. -func warnDefaultComponentConfigValue(componentConfigKind, paramName string, defaultValue, userValue interface{}) { - klog.Warningf("The recommended value for %q in %q is: %v; the provided value is: %v", - paramName, componentConfigKind, defaultValue, userValue) -} diff --git a/cmd/kubeadm/app/componentconfigs/kubelet.go b/cmd/kubeadm/app/componentconfigs/kubelet.go new file mode 100644 index 00000000000..ac9584983ce --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/kubelet.go @@ -0,0 +1,176 @@ +/* +Copyright 2019 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 ( + "path/filepath" + + "k8s.io/apimachinery/pkg/util/version" + clientset "k8s.io/client-go/kubernetes" + kubeletconfig "k8s.io/kubelet/config/v1beta1" + utilpointer "k8s.io/utils/pointer" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" +) + +const ( + // KubeletGroup is a pointer to the used API group name for the kubelet config + KubeletGroup = kubeletconfig.GroupName + + // kubeletReadOnlyPort specifies the default insecure http server port + // 0 will disable insecure http server. + kubeletReadOnlyPort int32 = 0 + + // kubeletRotateCertificates specifies the default value to enable certificate rotation + kubeletRotateCertificates = true + + // kubeletAuthenticationAnonymousEnabled specifies the default value to disable anonymous access + kubeletAuthenticationAnonymousEnabled = false + + // kubeletAuthenticationWebhookEnabled set the default value to enable authentication webhook + kubeletAuthenticationWebhookEnabled = true + + // kubeletHealthzBindAddress specifies the default healthz bind address + kubeletHealthzBindAddress = "127.0.0.1" +) + +// kubeletHandler is the handler instance for the kubelet component config +var kubeletHandler = handler{ + GroupVersion: kubeletconfig.SchemeGroupVersion, + AddToScheme: kubeletconfig.AddToScheme, + CreateEmpty: func() kubeadmapi.ComponentConfig { + return &kubeletConfig{} + }, + fromCluster: kubeletConfigFromCluster, +} + +func kubeletConfigFromCluster(h *handler, clientset clientset.Interface, clusterCfg *kubeadmapi.ClusterConfiguration) (kubeadmapi.ComponentConfig, error) { + // Read the ConfigMap from the cluster based on what version the kubelet is + k8sVersion, err := version.ParseGeneric(clusterCfg.KubernetesVersion) + if err != nil { + return nil, err + } + + configMapName := constants.GetKubeletConfigMapName(k8sVersion) + return h.fromConfigMap(clientset, configMapName, constants.KubeletBaseConfigurationConfigMapKey, true) +} + +// kubeletConfig implements the kubeadmapi.ComponentConfig interface for kubelet +type kubeletConfig struct { + config kubeletconfig.KubeletConfiguration +} + +func (kc *kubeletConfig) DeepCopy() kubeadmapi.ComponentConfig { + result := &kubeletConfig{} + kc.config.DeepCopyInto(&result.config) + return result +} + +func (kc *kubeletConfig) Marshal() ([]byte, error) { + return kubeletHandler.Marshal(&kc.config) +} + +func (kc *kubeletConfig) Unmarshal(docmap kubeadmapi.DocumentMap) error { + return kubeletHandler.Unmarshal(docmap, &kc.config) +} + +func (kc *kubeletConfig) Default(cfg *kubeadmapi.ClusterConfiguration, _ *kubeadmapi.APIEndpoint) { + const kind = "KubeletConfiguration" + + if kc.config.FeatureGates == nil { + kc.config.FeatureGates = map[string]bool{} + } + + if kc.config.StaticPodPath == "" { + kc.config.StaticPodPath = kubeadmapiv1beta2.DefaultManifestsDir + } else if kc.config.StaticPodPath != kubeadmapiv1beta2.DefaultManifestsDir { + warnDefaultComponentConfigValue(kind, "staticPodPath", kubeadmapiv1beta2.DefaultManifestsDir, kc.config.StaticPodPath) + } + + clusterDNS := "" + dnsIP, err := constants.GetDNSIP(cfg.Networking.ServiceSubnet, features.Enabled(cfg.FeatureGates, features.IPv6DualStack)) + if err != nil { + clusterDNS = kubeadmapiv1beta2.DefaultClusterDNSIP + } else { + clusterDNS = dnsIP.String() + } + + if kc.config.ClusterDNS == nil { + kc.config.ClusterDNS = []string{clusterDNS} + } else if len(kc.config.ClusterDNS) != 1 || kc.config.ClusterDNS[0] != clusterDNS { + warnDefaultComponentConfigValue(kind, "clusterDNS", []string{clusterDNS}, kc.config.ClusterDNS) + } + + if kc.config.ClusterDomain == "" { + kc.config.ClusterDomain = cfg.Networking.DNSDomain + } else if cfg.Networking.DNSDomain != "" && kc.config.ClusterDomain != cfg.Networking.DNSDomain { + warnDefaultComponentConfigValue(kind, "clusterDomain", cfg.Networking.DNSDomain, kc.config.ClusterDomain) + } + + // Require all clients to the kubelet API to have client certs signed by the cluster CA + clientCAFile := filepath.Join(cfg.CertificatesDir, constants.CACertName) + if kc.config.Authentication.X509.ClientCAFile == "" { + kc.config.Authentication.X509.ClientCAFile = clientCAFile + } else if kc.config.Authentication.X509.ClientCAFile != clientCAFile { + warnDefaultComponentConfigValue(kind, "authentication.x509.clientCAFile", clientCAFile, kc.config.Authentication.X509.ClientCAFile) + } + + if kc.config.Authentication.Anonymous.Enabled == nil { + kc.config.Authentication.Anonymous.Enabled = utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled) + } else if *kc.config.Authentication.Anonymous.Enabled != kubeletAuthenticationAnonymousEnabled { + warnDefaultComponentConfigValue(kind, "authentication.anonymous.enabled", kubeletAuthenticationAnonymousEnabled, *kc.config.Authentication.Anonymous.Enabled) + } + + // 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 + if kc.config.Authorization.Mode == "" { + kc.config.Authorization.Mode = kubeletconfig.KubeletAuthorizationModeWebhook + } else if kc.config.Authorization.Mode != kubeletconfig.KubeletAuthorizationModeWebhook { + warnDefaultComponentConfigValue(kind, "authorization.mode", kubeletconfig.KubeletAuthorizationModeWebhook, kc.config.Authorization.Mode) + } + + // Let clients using other authentication methods like ServiceAccount tokens also access the kubelet API + if kc.config.Authentication.Webhook.Enabled == nil { + kc.config.Authentication.Webhook.Enabled = utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled) + } else if *kc.config.Authentication.Webhook.Enabled != kubeletAuthenticationWebhookEnabled { + warnDefaultComponentConfigValue(kind, "authentication.webhook.enabled", kubeletAuthenticationWebhookEnabled, *kc.config.Authentication.Webhook.Enabled) + } + + // Serve a /healthz webserver on localhost:10248 that kubeadm can talk to + if kc.config.HealthzBindAddress == "" { + kc.config.HealthzBindAddress = kubeletHealthzBindAddress + } else if kc.config.HealthzBindAddress != kubeletHealthzBindAddress { + warnDefaultComponentConfigValue(kind, "healthzBindAddress", kubeletHealthzBindAddress, kc.config.HealthzBindAddress) + } + + if kc.config.HealthzPort == nil { + kc.config.HealthzPort = utilpointer.Int32Ptr(constants.KubeletHealthzPort) + } else if *kc.config.HealthzPort != constants.KubeletHealthzPort { + warnDefaultComponentConfigValue(kind, "healthzPort", constants.KubeletHealthzPort, *kc.config.HealthzPort) + } + + if kc.config.ReadOnlyPort != kubeletReadOnlyPort { + warnDefaultComponentConfigValue(kind, "readOnlyPort", kubeletReadOnlyPort, kc.config.ReadOnlyPort) + } + + // We cannot show a warning for RotateCertificates==false and we must hardcode it to true. + // There is no way to determine if the user has set this or not, given the field is a non-pointer. + kc.config.RotateCertificates = kubeletRotateCertificates +} diff --git a/cmd/kubeadm/app/componentconfigs/kubelet_test.go b/cmd/kubeadm/app/componentconfigs/kubelet_test.go new file mode 100644 index 00000000000..8f7549fcc04 --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/kubelet_test.go @@ -0,0 +1,488 @@ +/* +Copyright 2019 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 ( + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/lithammer/dedent" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/version" + clientsetfake "k8s.io/client-go/kubernetes/fake" + kubeletconfig "k8s.io/kubelet/config/v1beta1" + utilpointer "k8s.io/utils/pointer" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" +) + +// kubeletMarshalCases holds common marshal test cases for both the marshal and unmarshal tests +var kubeletMarshalCases = []struct { + name string + obj *kubeletConfig + yaml string +}{ + { + name: "Empty config", + obj: &kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{}, + }, + yaml: dedent.Dedent(` + apiVersion: kubelet.config.k8s.io/v1beta1 + authentication: + anonymous: {} + webhook: + cacheTTL: 0s + x509: {} + authorization: + webhook: + cacheAuthorizedTTL: 0s + cacheUnauthorizedTTL: 0s + cpuManagerReconcilePeriod: 0s + evictionPressureTransitionPeriod: 0s + fileCheckFrequency: 0s + httpCheckFrequency: 0s + imageMinimumGCAge: 0s + kind: KubeletConfiguration + nodeStatusReportFrequency: 0s + nodeStatusUpdateFrequency: 0s + runtimeRequestTimeout: 0s + streamingConnectionIdleTimeout: 0s + syncFrequency: 0s + volumeStatsAggPeriod: 0s + `), + }, + { + name: "Non empty config", + obj: &kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{ + Address: "1.2.3.4", + Port: 12345, + RotateCertificates: true, + }, + }, + yaml: dedent.Dedent(` + address: 1.2.3.4 + apiVersion: kubelet.config.k8s.io/v1beta1 + authentication: + anonymous: {} + webhook: + cacheTTL: 0s + x509: {} + authorization: + webhook: + cacheAuthorizedTTL: 0s + cacheUnauthorizedTTL: 0s + cpuManagerReconcilePeriod: 0s + evictionPressureTransitionPeriod: 0s + fileCheckFrequency: 0s + httpCheckFrequency: 0s + imageMinimumGCAge: 0s + kind: KubeletConfiguration + nodeStatusReportFrequency: 0s + nodeStatusUpdateFrequency: 0s + port: 12345 + rotateCertificates: true + runtimeRequestTimeout: 0s + streamingConnectionIdleTimeout: 0s + syncFrequency: 0s + volumeStatsAggPeriod: 0s + `), + }, +} + +func TestKubeletMarshal(t *testing.T) { + for _, test := range kubeletMarshalCases { + t.Run(test.name, func(t *testing.T) { + b, err := test.obj.Marshal() + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + got := strings.TrimSpace(string(b)) + expected := strings.TrimSpace(test.yaml) + if expected != string(got) { + t.Fatalf("Missmatch between expected and got:\nExpected:\n%s\n---\nGot:\n%s", expected, string(got)) + } + }) + } +} + +func TestKubeletUnmarshal(t *testing.T) { + for _, test := range kubeletMarshalCases { + t.Run(test.name, func(t *testing.T) { + gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(test.yaml)) + if err != nil { + t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err) + } + + got := &kubeletConfig{} + if err = got.Unmarshal(gvkmap); err != nil { + t.Fatalf("unexpected failure of Unmarshal: %v", err) + } + + expected := test.obj.DeepCopy().(*kubeletConfig) + expected.config.APIVersion = kubeletHandler.GroupVersion.String() + expected.config.Kind = "KubeletConfiguration" + + if !reflect.DeepEqual(got, expected) { + t.Fatalf("Missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", expected, got) + } + }) + } +} + +func TestKubeletDefault(t *testing.T) { + tests := []struct { + name string + clusterCfg kubeadmapi.ClusterConfiguration + expected kubeletConfig + }{ + { + name: "No specific defaulting works", + clusterCfg: kubeadmapi.ClusterConfiguration{}, + expected: kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{ + FeatureGates: map[string]bool{}, + StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir, + ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP}, + Authentication: kubeletconfig.KubeletAuthentication{ + X509: kubeletconfig.KubeletX509Authentication{ + ClientCAFile: constants.CACertName, + }, + Anonymous: kubeletconfig.KubeletAnonymousAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled), + }, + Webhook: kubeletconfig.KubeletWebhookAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled), + }, + }, + Authorization: kubeletconfig.KubeletAuthorization{ + Mode: kubeletconfig.KubeletAuthorizationModeWebhook, + }, + HealthzBindAddress: kubeletHealthzBindAddress, + HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort), + RotateCertificates: kubeletRotateCertificates, + }, + }, + }, + { + name: "Service subnet, no dual stack defaulting works", + clusterCfg: kubeadmapi.ClusterConfiguration{ + Networking: kubeadmapi.Networking{ + ServiceSubnet: "192.168.0.0/16", + }, + }, + expected: kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{ + FeatureGates: map[string]bool{}, + StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir, + ClusterDNS: []string{"192.168.0.10"}, + Authentication: kubeletconfig.KubeletAuthentication{ + X509: kubeletconfig.KubeletX509Authentication{ + ClientCAFile: constants.CACertName, + }, + Anonymous: kubeletconfig.KubeletAnonymousAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled), + }, + Webhook: kubeletconfig.KubeletWebhookAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled), + }, + }, + Authorization: kubeletconfig.KubeletAuthorization{ + Mode: kubeletconfig.KubeletAuthorizationModeWebhook, + }, + HealthzBindAddress: kubeletHealthzBindAddress, + HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort), + RotateCertificates: kubeletRotateCertificates, + }, + }, + }, + { + name: "Service subnet, dual stack defaulting works", + clusterCfg: kubeadmapi.ClusterConfiguration{ + FeatureGates: map[string]bool{ + features.IPv6DualStack: true, + }, + Networking: kubeadmapi.Networking{ + ServiceSubnet: "192.168.0.0/16", + }, + }, + expected: kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{ + FeatureGates: map[string]bool{}, + StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir, + ClusterDNS: []string{"192.168.0.10"}, + Authentication: kubeletconfig.KubeletAuthentication{ + X509: kubeletconfig.KubeletX509Authentication{ + ClientCAFile: constants.CACertName, + }, + Anonymous: kubeletconfig.KubeletAnonymousAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled), + }, + Webhook: kubeletconfig.KubeletWebhookAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled), + }, + }, + Authorization: kubeletconfig.KubeletAuthorization{ + Mode: kubeletconfig.KubeletAuthorizationModeWebhook, + }, + HealthzBindAddress: kubeletHealthzBindAddress, + HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort), + RotateCertificates: kubeletRotateCertificates, + }, + }, + }, + { + name: "DNS domain defaulting works", + clusterCfg: kubeadmapi.ClusterConfiguration{ + Networking: kubeadmapi.Networking{ + DNSDomain: "example.com", + }, + }, + expected: kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{ + FeatureGates: map[string]bool{}, + StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir, + ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP}, + ClusterDomain: "example.com", + Authentication: kubeletconfig.KubeletAuthentication{ + X509: kubeletconfig.KubeletX509Authentication{ + ClientCAFile: constants.CACertName, + }, + Anonymous: kubeletconfig.KubeletAnonymousAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled), + }, + Webhook: kubeletconfig.KubeletWebhookAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled), + }, + }, + Authorization: kubeletconfig.KubeletAuthorization{ + Mode: kubeletconfig.KubeletAuthorizationModeWebhook, + }, + HealthzBindAddress: kubeletHealthzBindAddress, + HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort), + RotateCertificates: kubeletRotateCertificates, + }, + }, + }, + { + name: "CertificatesDir defaulting works", + clusterCfg: kubeadmapi.ClusterConfiguration{ + CertificatesDir: "/path/to/certs", + }, + expected: kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{ + FeatureGates: map[string]bool{}, + StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir, + ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP}, + Authentication: kubeletconfig.KubeletAuthentication{ + X509: kubeletconfig.KubeletX509Authentication{ + ClientCAFile: filepath.Join("/path/to/certs", constants.CACertName), + }, + Anonymous: kubeletconfig.KubeletAnonymousAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled), + }, + Webhook: kubeletconfig.KubeletWebhookAuthentication{ + Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled), + }, + }, + Authorization: kubeletconfig.KubeletAuthorization{ + Mode: kubeletconfig.KubeletAuthorizationModeWebhook, + }, + HealthzBindAddress: kubeletHealthzBindAddress, + HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort), + RotateCertificates: kubeletRotateCertificates, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := &kubeletConfig{} + got.Default(&test.clusterCfg, &kubeadmapi.APIEndpoint{}) + if !reflect.DeepEqual(got, &test.expected) { + t.Fatalf("Missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", test.expected, got) + } + }) + } +} + +// runKubeletFromTest holds common test case data and evaluation code for kubeletHandler.From* functions +func runKubeletFromTest(t *testing.T, perform func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error)) { + tests := []struct { + name string + in string + out *kubeletConfig + expectErr bool + }{ + { + name: "Empty document map should return nothing successfully", + }, + { + name: "Non-empty non-kubelet document map returns nothing successfully", + in: dedent.Dedent(` + apiVersion: api.example.com/v1 + kind: Configuration + `), + }, + { + name: "Old kubelet version returns an error", + in: dedent.Dedent(` + apiVersion: kubelet.config.k8s.io/v1alpha1 + kind: KubeletConfiguration + `), + expectErr: true, + }, + { + name: "New kubelet version returns an error", + in: dedent.Dedent(` + apiVersion: kubelet.config.k8s.io/v1 + kind: KubeletConfiguration + `), + expectErr: true, + }, + { + name: "Wrong kubelet kind returns an error", + in: dedent.Dedent(` + apiVersion: kubelet.config.k8s.io/v1beta1 + kind: Configuration + `), + expectErr: true, + }, + { + name: "Valid kubelet only config gets loaded", + in: dedent.Dedent(` + apiVersion: kubelet.config.k8s.io/v1beta1 + kind: KubeletConfiguration + address: 1.2.3.4 + port: 12345 + rotateCertificates: true + `), + out: &kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kubeletHandler.GroupVersion.String(), + Kind: "KubeletConfiguration", + }, + Address: "1.2.3.4", + Port: 12345, + RotateCertificates: true, + }, + }, + }, + { + name: "Valid kubelet config gets loaded when coupled with an extra document", + in: dedent.Dedent(` + apiVersion: api.example.com/v1 + kind: Configuration + --- + apiVersion: kubelet.config.k8s.io/v1beta1 + kind: KubeletConfiguration + address: 1.2.3.4 + port: 12345 + rotateCertificates: true + `), + out: &kubeletConfig{ + config: kubeletconfig.KubeletConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kubeletHandler.GroupVersion.String(), + Kind: "KubeletConfiguration", + }, + Address: "1.2.3.4", + Port: 12345, + RotateCertificates: true, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + componentCfg, err := perform(t, test.in) + if err != nil { + if !test.expectErr { + t.Errorf("unexpected failure: %v", err) + } + } else { + if test.expectErr { + t.Error("unexpected success") + } else { + if componentCfg == nil { + if test.out != nil { + t.Error("unexpected nil result") + } + } else { + if got, ok := componentCfg.(*kubeletConfig); !ok { + t.Error("different result type") + } else { + if test.out == nil { + t.Errorf("unexpected result: %v", got) + } else if !reflect.DeepEqual(test.out, got) { + t.Errorf("missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", test.out, got) + } + } + } + } + } + }) + } +} + +func TestKubeletFromDocumentMap(t *testing.T) { + runKubeletFromTest(t, func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error) { + gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(in)) + if err != nil { + t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err) + } + + return kubeletHandler.FromDocumentMap(gvkmap) + }) +} + +func TestKubeletFromCluster(t *testing.T) { + runKubeletFromTest(t, func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error) { + clusterCfg := &kubeadmapi.ClusterConfiguration{ + KubernetesVersion: constants.CurrentKubernetesVersion.String(), + } + + k8sVersion := version.MustParseGeneric(clusterCfg.KubernetesVersion) + + client := clientsetfake.NewSimpleClientset( + &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.GetKubeletConfigMapName(k8sVersion), + Namespace: metav1.NamespaceSystem, + }, + Data: map[string]string{ + constants.KubeletBaseConfigurationConfigMapKey: in, + }, + }, + ) + + return kubeletHandler.FromCluster(client, clusterCfg) + }) +} diff --git a/cmd/kubeadm/app/componentconfigs/kubeproxy.go b/cmd/kubeadm/app/componentconfigs/kubeproxy.go new file mode 100644 index 00000000000..da30e1af45b --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/kubeproxy.go @@ -0,0 +1,114 @@ +/* +Copyright 2019 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 ( + "net" + + clientset "k8s.io/client-go/kubernetes" + kubeproxyconfig "k8s.io/kube-proxy/config/v1alpha1" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" +) + +const ( + // KubeProxyGroup is a pointer to the used API group name for the kube-proxy config + KubeProxyGroup = kubeproxyconfig.GroupName + + // kubeproxyKubeConfigFileName is used during defaulting. It's here so it can be accessed from the tests. + kubeproxyKubeConfigFileName = "/var/lib/kube-proxy/kubeconfig.conf" +) + +// kubeProxyHandler is the handler instance for the kube-proxy component config +var kubeProxyHandler = handler{ + GroupVersion: kubeproxyconfig.SchemeGroupVersion, + AddToScheme: kubeproxyconfig.AddToScheme, + CreateEmpty: func() kubeadmapi.ComponentConfig { + return &kubeProxyConfig{} + }, + fromCluster: kubeProxyConfigFromCluster, +} + +func kubeProxyConfigFromCluster(h *handler, clientset clientset.Interface, _ *kubeadmapi.ClusterConfiguration) (kubeadmapi.ComponentConfig, error) { + return h.fromConfigMap(clientset, kubeadmconstants.KubeProxyConfigMap, kubeadmconstants.KubeProxyConfigMapKey, false) +} + +// kubeProxyConfig implements the kubeadmapi.ComponentConfig interface for kube-proxy +type kubeProxyConfig struct { + config kubeproxyconfig.KubeProxyConfiguration +} + +func (kp *kubeProxyConfig) DeepCopy() kubeadmapi.ComponentConfig { + result := &kubeProxyConfig{} + kp.config.DeepCopyInto(&result.config) + return result +} + +func (kp *kubeProxyConfig) Marshal() ([]byte, error) { + return kubeProxyHandler.Marshal(&kp.config) +} + +func (kp *kubeProxyConfig) Unmarshal(docmap kubeadmapi.DocumentMap) error { + return kubeProxyHandler.Unmarshal(docmap, &kp.config) +} + +func kubeProxyDefaultBindAddress(localAdvertiseAddress string) string { + ip := net.ParseIP(localAdvertiseAddress) + if ip.To4() != nil { + return kubeadmapiv1beta2.DefaultProxyBindAddressv4 + } + return kubeadmapiv1beta2.DefaultProxyBindAddressv6 +} + +func (kp *kubeProxyConfig) Default(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint *kubeadmapi.APIEndpoint) { + const kind = "KubeProxyConfiguration" + + // The below code is necessary because while KubeProxy may be defined, the user may not + // have defined any feature-gates, thus FeatureGates will be nil and the later insertion + // of any feature-gates (e.g. IPv6DualStack) will cause a panic. + if kp.config.FeatureGates == nil { + kp.config.FeatureGates = map[string]bool{} + } + + defaultBindAddress := kubeProxyDefaultBindAddress(localAPIEndpoint.AdvertiseAddress) + if kp.config.BindAddress == "" { + kp.config.BindAddress = defaultBindAddress + } else if kp.config.BindAddress != defaultBindAddress { + warnDefaultComponentConfigValue(kind, "bindAddress", kp.config.BindAddress, defaultBindAddress) + } + + if kp.config.ClusterCIDR == "" && cfg.Networking.PodSubnet != "" { + kp.config.ClusterCIDR = cfg.Networking.PodSubnet + } else if cfg.Networking.PodSubnet != "" && kp.config.ClusterCIDR != cfg.Networking.PodSubnet { + warnDefaultComponentConfigValue(kind, "clusterCIDR", cfg.Networking.PodSubnet, kp.config.ClusterCIDR) + } + + if kp.config.ClientConnection.Kubeconfig == "" { + kp.config.ClientConnection.Kubeconfig = kubeproxyKubeConfigFileName + } else if kp.config.ClientConnection.Kubeconfig != kubeproxyKubeConfigFileName { + warnDefaultComponentConfigValue(kind, "clientConnection.kubeconfig", kubeproxyKubeConfigFileName, kp.config.ClientConnection.Kubeconfig) + } + + // TODO: The following code should be removed after dual-stack is GA. + // Note: The user still retains the ability to explicitly set feature-gates and that value will overwrite this base value. + if enabled, present := cfg.FeatureGates[features.IPv6DualStack]; present { + kp.config.FeatureGates[features.IPv6DualStack] = enabled + } +} diff --git a/cmd/kubeadm/app/componentconfigs/kubeproxy_test.go b/cmd/kubeadm/app/componentconfigs/kubeproxy_test.go new file mode 100644 index 00000000000..a3ab50d7e0a --- /dev/null +++ b/cmd/kubeadm/app/componentconfigs/kubeproxy_test.go @@ -0,0 +1,440 @@ +/* +Copyright 2019 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 ( + "reflect" + "strings" + "testing" + + "github.com/lithammer/dedent" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientsetfake "k8s.io/client-go/kubernetes/fake" + componentbaseconfig "k8s.io/component-base/config/v1alpha1" + kubeproxyconfig "k8s.io/kube-proxy/config/v1alpha1" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" + "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" +) + +// kubeProxyMarshalCases holds common marshal test cases for both the marshal and unmarshal tests +var kubeProxyMarshalCases = []struct { + name string + obj *kubeProxyConfig + yaml string +}{ + { + name: "Empty config", + obj: &kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{}, + }, + yaml: dedent.Dedent(` + apiVersion: kubeproxy.config.k8s.io/v1alpha1 + bindAddress: "" + clientConnection: + acceptContentTypes: "" + burst: 0 + contentType: "" + kubeconfig: "" + qps: 0 + clusterCIDR: "" + configSyncPeriod: 0s + conntrack: + maxPerCore: null + min: null + tcpCloseWaitTimeout: null + tcpEstablishedTimeout: null + enableProfiling: false + healthzBindAddress: "" + hostnameOverride: "" + iptables: + masqueradeAll: false + masqueradeBit: null + minSyncPeriod: 0s + syncPeriod: 0s + ipvs: + excludeCIDRs: null + minSyncPeriod: 0s + scheduler: "" + strictARP: false + syncPeriod: 0s + kind: KubeProxyConfiguration + metricsBindAddress: "" + mode: "" + nodePortAddresses: null + oomScoreAdj: null + portRange: "" + udpIdleTimeout: 0s + winkernel: + enableDSR: false + networkName: "" + sourceVip: "" + `), + }, + { + name: "Non empty config", + obj: &kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{ + BindAddress: "1.2.3.4", + EnableProfiling: true, + }, + }, + yaml: dedent.Dedent(` + apiVersion: kubeproxy.config.k8s.io/v1alpha1 + bindAddress: 1.2.3.4 + clientConnection: + acceptContentTypes: "" + burst: 0 + contentType: "" + kubeconfig: "" + qps: 0 + clusterCIDR: "" + configSyncPeriod: 0s + conntrack: + maxPerCore: null + min: null + tcpCloseWaitTimeout: null + tcpEstablishedTimeout: null + enableProfiling: true + healthzBindAddress: "" + hostnameOverride: "" + iptables: + masqueradeAll: false + masqueradeBit: null + minSyncPeriod: 0s + syncPeriod: 0s + ipvs: + excludeCIDRs: null + minSyncPeriod: 0s + scheduler: "" + strictARP: false + syncPeriod: 0s + kind: KubeProxyConfiguration + metricsBindAddress: "" + mode: "" + nodePortAddresses: null + oomScoreAdj: null + portRange: "" + udpIdleTimeout: 0s + winkernel: + enableDSR: false + networkName: "" + sourceVip: "" + `), + }, +} + +func TestKubeProxyMarshal(t *testing.T) { + for _, test := range kubeProxyMarshalCases { + t.Run(test.name, func(t *testing.T) { + b, err := test.obj.Marshal() + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + got := strings.TrimSpace(string(b)) + expected := strings.TrimSpace(test.yaml) + if expected != string(got) { + t.Fatalf("Missmatch between expected and got:\nExpected:\n%s\n---\nGot:\n%s", expected, string(got)) + } + }) + } +} + +func TestKubeProxyUnmarshal(t *testing.T) { + for _, test := range kubeProxyMarshalCases { + t.Run(test.name, func(t *testing.T) { + gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(test.yaml)) + if err != nil { + t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err) + } + + got := &kubeProxyConfig{} + if err = got.Unmarshal(gvkmap); err != nil { + t.Fatalf("unexpected failure of Unmarshal: %v", err) + } + + expected := test.obj.DeepCopy().(*kubeProxyConfig) + expected.config.APIVersion = kubeProxyHandler.GroupVersion.String() + expected.config.Kind = "KubeProxyConfiguration" + + if !reflect.DeepEqual(got, expected) { + t.Fatalf("Missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", expected, got) + } + }) + } +} + +func TestKubeProxyDefault(t *testing.T) { + tests := []struct { + name string + clusterCfg kubeadmapi.ClusterConfiguration + endpoint kubeadmapi.APIEndpoint + expected kubeProxyConfig + }{ + { + name: "No specific defaulting works", + clusterCfg: kubeadmapi.ClusterConfiguration{}, + endpoint: kubeadmapi.APIEndpoint{}, + expected: kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{ + FeatureGates: map[string]bool{}, + BindAddress: kubeadmapiv1beta2.DefaultProxyBindAddressv6, + ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ + Kubeconfig: kubeproxyKubeConfigFileName, + }, + }, + }, + }, + { + name: "IPv4 bind address", + clusterCfg: kubeadmapi.ClusterConfiguration{}, + endpoint: kubeadmapi.APIEndpoint{ + AdvertiseAddress: "1.2.3.4", + }, + expected: kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{ + FeatureGates: map[string]bool{}, + BindAddress: kubeadmapiv1beta2.DefaultProxyBindAddressv4, + ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ + Kubeconfig: kubeproxyKubeConfigFileName, + }, + }, + }, + }, + { + name: "ClusterCIDR is fetched from PodSubnet", + clusterCfg: kubeadmapi.ClusterConfiguration{ + Networking: kubeadmapi.Networking{ + PodSubnet: "192.168.0.0/16", + }, + }, + endpoint: kubeadmapi.APIEndpoint{}, + expected: kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{ + FeatureGates: map[string]bool{}, + BindAddress: kubeadmapiv1beta2.DefaultProxyBindAddressv6, + ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ + Kubeconfig: kubeproxyKubeConfigFileName, + }, + ClusterCIDR: "192.168.0.0/16", + }, + }, + }, + { + name: "IPv6DualStack feature gate set to true", + clusterCfg: kubeadmapi.ClusterConfiguration{ + FeatureGates: map[string]bool{ + features.IPv6DualStack: true, + }, + }, + endpoint: kubeadmapi.APIEndpoint{}, + expected: kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{ + FeatureGates: map[string]bool{ + features.IPv6DualStack: true, + }, + BindAddress: kubeadmapiv1beta2.DefaultProxyBindAddressv6, + ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ + Kubeconfig: kubeproxyKubeConfigFileName, + }, + }, + }, + }, + { + name: "IPv6DualStack feature gate set to false", + clusterCfg: kubeadmapi.ClusterConfiguration{ + FeatureGates: map[string]bool{ + features.IPv6DualStack: false, + }, + }, + endpoint: kubeadmapi.APIEndpoint{}, + expected: kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{ + FeatureGates: map[string]bool{ + features.IPv6DualStack: false, + }, + BindAddress: kubeadmapiv1beta2.DefaultProxyBindAddressv6, + ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ + Kubeconfig: kubeproxyKubeConfigFileName, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := &kubeProxyConfig{} + got.Default(&test.clusterCfg, &test.endpoint) + if !reflect.DeepEqual(got, &test.expected) { + t.Fatalf("Missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", test.expected, got) + } + }) + } +} + +// runKubeProxyFromTest holds common test case data and evaluation code for kubeProxyHandler.From* functions +func runKubeProxyFromTest(t *testing.T, perform func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error)) { + tests := []struct { + name string + in string + out *kubeProxyConfig + expectErr bool + }{ + { + name: "Empty document map should return nothing successfully", + }, + { + name: "Non-empty non-kube-proxy document map returns nothing successfully", + in: dedent.Dedent(` + apiVersion: api.example.com/v1 + kind: Configuration + `), + }, + { + name: "Old kube-proxy version returns an error", + in: dedent.Dedent(` + apiVersion: kubeproxy.config.k8s.io/v1alpha0 + kind: KubeProxyConfiguration + `), + expectErr: true, + }, + { + name: "New kube-proxy version returns an error", + in: dedent.Dedent(` + apiVersion: kubeproxy.config.k8s.io/v1beta1 + kind: KubeProxyConfiguration + `), + expectErr: true, + }, + { + name: "Wrong kube-proxy kind returns an error", + in: dedent.Dedent(` + apiVersion: kubeproxy.config.k8s.io/v1alpha1 + kind: Configuration + `), + expectErr: true, + }, + { + name: "Valid kube-proxy only config gets loaded", + in: dedent.Dedent(` + apiVersion: kubeproxy.config.k8s.io/v1alpha1 + kind: KubeProxyConfiguration + bindAddress: 1.2.3.4 + enableProfiling: true + `), + out: &kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kubeProxyHandler.GroupVersion.String(), + Kind: "KubeProxyConfiguration", + }, + BindAddress: "1.2.3.4", + EnableProfiling: true, + }, + }, + }, + { + name: "Valid kube-proxy config gets loaded when coupled with an extra document", + in: dedent.Dedent(` + apiVersion: api.example.com/v1 + kind: Configuration + --- + apiVersion: kubeproxy.config.k8s.io/v1alpha1 + kind: KubeProxyConfiguration + bindAddress: 1.2.3.4 + enableProfiling: true + `), + out: &kubeProxyConfig{ + config: kubeproxyconfig.KubeProxyConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kubeProxyHandler.GroupVersion.String(), + Kind: "KubeProxyConfiguration", + }, + BindAddress: "1.2.3.4", + EnableProfiling: true, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + componentCfg, err := perform(t, test.in) + if err != nil { + if !test.expectErr { + t.Errorf("unexpected failure: %v", err) + } + } else { + if test.expectErr { + t.Error("unexpected success") + } else { + if componentCfg == nil { + if test.out != nil { + t.Error("unexpected nil result") + } + } else { + if got, ok := componentCfg.(*kubeProxyConfig); !ok { + t.Error("different result type") + } else { + if test.out == nil { + t.Errorf("unexpected result: %v", got) + } else if !reflect.DeepEqual(test.out, got) { + t.Errorf("missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", test.out, got) + } + } + } + } + } + }) + } +} + +func TestKubeProxyFromDocumentMap(t *testing.T) { + runKubeProxyFromTest(t, func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error) { + gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(in)) + if err != nil { + t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err) + } + + return kubeProxyHandler.FromDocumentMap(gvkmap) + }) +} + +func TestKubeProxyFromCluster(t *testing.T) { + runKubeProxyFromTest(t, func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error) { + client := clientsetfake.NewSimpleClientset( + &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.KubeProxyConfigMap, + Namespace: metav1.NamespaceSystem, + }, + Data: map[string]string{ + constants.KubeProxyConfigMapKey: in, + }, + }, + ) + + return kubeProxyHandler.FromCluster(client, &kubeadmapi.ClusterConfiguration{}) + }) +} diff --git a/cmd/kubeadm/app/componentconfigs/registrations.go b/cmd/kubeadm/app/componentconfigs/registrations.go deleted file mode 100644 index 4f516adf325..00000000000 --- a/cmd/kubeadm/app/componentconfigs/registrations.go +++ /dev/null @@ -1,158 +0,0 @@ -/* -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" - "k8s.io/apimachinery/pkg/util/version" - clientset "k8s.io/client-go/kubernetes" - kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" - kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" -) - -// 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.ClusterConfiguration) - // ValidateFunc is a function that should validate the ComponentConfig type embedded in the internal kubeadm config struct - ValidateFunc func(*kubeadmapi.ClusterConfiguration, *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.ClusterConfiguration) (runtime.Object, bool) - // SetToInternalConfig sets the pointer to a ComponentConfig API object embedded in the internal kubeadm config struct - SetToInternalConfig func(runtime.Object, *kubeadmapi.ClusterConfiguration) bool - // GetFromConfigMap returns the pointer to the ComponentConfig API object read from the config map stored in the cluster - GetFromConfigMap func(clientset.Interface, *version.Version) (runtime.Object, error) -} - -// 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 - if err := unmarshalObject(obj, fileContent); err != nil { - return nil, err - } - return obj, nil -} - -func unmarshalObject(obj runtime.Object, fileContent []byte) error { - // Decode the file content using the componentconfig Codecs that knows about all APIs - return runtime.DecodeInto(Codecs.UniversalDecoder(), fileContent, obj) -} - -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 must use v1alpha1scheme defined in k8s.io/kubernetes, because the schema defined in k8s.io/kube-proxy doesn't have defaulting functions - AddToSchemeFuncs: []AddToSchemeFunc{kubeproxyconfigv1alpha1.AddToScheme}, - DefaulterFunc: DefaultKubeProxyConfiguration, - ValidateFunc: NoValidator("kube-proxy"), - EmptyValue: &kubeproxyconfigv1alpha1.KubeProxyConfiguration{}, - GetFromInternalConfig: func(cfg *kubeadmapi.ClusterConfiguration) (runtime.Object, bool) { - return cfg.ComponentConfigs.KubeProxy, cfg.ComponentConfigs.KubeProxy != nil - }, - SetToInternalConfig: func(obj runtime.Object, cfg *kubeadmapi.ClusterConfiguration) bool { - kubeproxyConfig, ok := obj.(*kubeproxyconfigv1alpha1.KubeProxyConfiguration) - if ok { - cfg.ComponentConfigs.KubeProxy = kubeproxyConfig - } - return ok - }, - GetFromConfigMap: GetFromKubeProxyConfigMap, - }, - KubeletConfigurationKind: { - MarshalGroupVersion: kubeletconfigv1beta1.SchemeGroupVersion, - // PAddToSchemeFuncs must use v1alpha1scheme defined in k8s.io/kubernetes, because the schema defined in k8s.io/kubelet doesn't have defaulting functions - AddToSchemeFuncs: []AddToSchemeFunc{kubeletconfigv1beta1.AddToScheme}, - DefaulterFunc: DefaultKubeletConfiguration, - ValidateFunc: NoValidator("kubelet"), - EmptyValue: &kubeletconfigv1beta1.KubeletConfiguration{}, - GetFromInternalConfig: func(cfg *kubeadmapi.ClusterConfiguration) (runtime.Object, bool) { - return cfg.ComponentConfigs.Kubelet, cfg.ComponentConfigs.Kubelet != nil - }, - SetToInternalConfig: func(obj runtime.Object, cfg *kubeadmapi.ClusterConfiguration) bool { - kubeletConfig, ok := obj.(*kubeletconfigv1beta1.KubeletConfiguration) - if ok { - cfg.ComponentConfigs.Kubelet = kubeletConfig - } - return ok - }, - GetFromConfigMap: GetFromKubeletConfigMap, - }, -} - -// 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.ClusterConfiguration) { - for _, registration := range *rs { - registration.DefaulterFunc(internalcfg) - } -} - -// Validate validates the ComponentConfig parts of the internal kubeadm API type -func (rs *Registrations) Validate(internalcfg *kubeadmapi.ClusterConfiguration) 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 index edb545416c8..c50f79c66bb 100644 --- a/cmd/kubeadm/app/componentconfigs/scheme.go +++ b/cmd/kubeadm/app/componentconfigs/scheme.go @@ -37,5 +37,7 @@ func init() { // AddToScheme builds the kubeadm ComponentConfig scheme using all known ComponentConfig versions. func AddToScheme(scheme *runtime.Scheme) { - utilruntime.Must(Known.AddToScheme(scheme)) + for _, handler := range known { + utilruntime.Must(handler.AddToScheme(scheme)) + } } diff --git a/cmd/kubeadm/app/componentconfigs/validation.go b/cmd/kubeadm/app/componentconfigs/utils.go similarity index 54% rename from cmd/kubeadm/app/componentconfigs/validation.go rename to cmd/kubeadm/app/componentconfigs/utils.go index ca6fc8e070f..a35d4977cc0 100644 --- a/cmd/kubeadm/app/componentconfigs/validation.go +++ b/cmd/kubeadm/app/componentconfigs/utils.go @@ -17,15 +17,12 @@ limitations under the License. package componentconfigs import ( - "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/klog" - kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) -// NoValidator returns a dummy validator function when no validation method is available for the component -func NoValidator(component string) func(*kubeadmapi.ClusterConfiguration, *field.Path) field.ErrorList { - return func(_ *kubeadmapi.ClusterConfiguration, _ *field.Path) field.ErrorList { - klog.Warningf("Cannot validate %s config - no validator is available", component) - return field.ErrorList{} - } +// warnDefaultComponentConfigValue prints a warning if the user modified a field in a certain +// CompomentConfig from the default recommended value in kubeadm. +func warnDefaultComponentConfigValue(componentConfigKind, paramName string, defaultValue, userValue interface{}) { + klog.Warningf("The recommended value for %q in %q is: %v; the provided value is: %v", + paramName, componentConfigKind, defaultValue, userValue) } diff --git a/cmd/kubeadm/app/phases/addons/proxy/BUILD b/cmd/kubeadm/app/phases/addons/proxy/BUILD index cedaeba93da..03a3dc7328b 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/BUILD +++ b/cmd/kubeadm/app/phases/addons/proxy/BUILD @@ -17,14 +17,11 @@ go_test( "//cmd/kubeadm/app/util/config:go_default_library", "//staging/src/k8s.io/api/apps/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library", "//staging/src/k8s.io/client-go/testing:go_default_library", - "//staging/src/k8s.io/kube-proxy/config/v1alpha1:go_default_library", - "//vendor/k8s.io/utils/pointer:go_default_library", ], ) diff --git a/cmd/kubeadm/app/phases/addons/proxy/proxy.go b/cmd/kubeadm/app/phases/addons/proxy/proxy.go index 36a840e1c36..17be0704ed4 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/proxy.go +++ b/cmd/kubeadm/app/phases/addons/proxy/proxy.go @@ -57,7 +57,12 @@ func EnsureProxyAddon(cfg *kubeadmapi.ClusterConfiguration, localEndpoint *kubea return err } - proxyBytes, err := componentconfigs.Known[componentconfigs.KubeProxyConfigurationKind].Marshal(cfg.ComponentConfigs.KubeProxy) + kubeProxyCfg, ok := cfg.ComponentConfigs[componentconfigs.KubeProxyGroup] + if !ok { + return errors.New("no kube-proxy component config found in the active component config set") + } + + proxyBytes, err := kubeProxyCfg.Marshal() if err != nil { return errors.Wrap(err, "error when marshaling") } diff --git a/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go b/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go index 686e9cca130..29f1b130d86 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go +++ b/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go @@ -19,22 +19,18 @@ package proxy import ( "strings" "testing" - "time" apps "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" clientsetfake "k8s.io/client-go/kubernetes/fake" clientsetscheme "k8s.io/client-go/kubernetes/scheme" core "k8s.io/client-go/testing" - kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" - "k8s.io/utils/pointer" ) func TestCreateServiceAccount(t *testing.T) { @@ -206,22 +202,6 @@ func TestEnsureProxyAddon(t *testing.T) { t.Errorf("test failed to convert external to internal version") return } - intControlPlane.ComponentConfigs.KubeProxy = &kubeproxyconfigv1alpha1.KubeProxyConfiguration{ - BindAddress: "", - HealthzBindAddress: "0.0.0.0:10256", - MetricsBindAddress: "127.0.0.1:10249", - Conntrack: kubeproxyconfigv1alpha1.KubeProxyConntrackConfiguration{ - MaxPerCore: pointer.Int32Ptr(1), - Min: pointer.Int32Ptr(1), - TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, - TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, - }, - } - // Run dynamic defaulting again as we changed the internal cfg - if err := configutil.SetInitDynamicDefaults(intControlPlane); err != nil { - t.Errorf("test failed to set dynamic defaults: %v", err) - return - } err = EnsureProxyAddon(&intControlPlane.ClusterConfiguration, &intControlPlane.LocalAPIEndpoint, client) // Compare actual to expected errors @@ -240,18 +220,6 @@ func TestEnsureProxyAddon(t *testing.T) { expErr, actErr) } - if intControlPlane.ComponentConfigs.KubeProxy.BindAddress != tc.expBindAddr { - t.Errorf("%s test failed, expected: %s, got: %s", - tc.name, - tc.expBindAddr, - intControlPlane.ComponentConfigs.KubeProxy.BindAddress) - } - if intControlPlane.ComponentConfigs.KubeProxy.ClusterCIDR != tc.expClusterCIDR { - t.Errorf("%s test failed, expected: %s, got: %s", - tc.name, - tc.expClusterCIDR, - intControlPlane.ComponentConfigs.KubeProxy.ClusterCIDR) - } }) } } diff --git a/cmd/kubeadm/app/phases/kubelet/BUILD b/cmd/kubeadm/app/phases/kubelet/BUILD index b547ed24254..dd6d78090d2 100644 --- a/cmd/kubeadm/app/phases/kubelet/BUILD +++ b/cmd/kubeadm/app/phases/kubelet/BUILD @@ -25,7 +25,6 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", - "//staging/src/k8s.io/kubelet/config/v1beta1:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", "//vendor/k8s.io/klog:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", @@ -42,14 +41,15 @@ go_test( embed = [":go_default_library"], deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/util/config:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", "//staging/src/k8s.io/client-go/testing:go_default_library", - "//staging/src/k8s.io/kubelet/config/v1beta1:go_default_library", "//vendor/github.com/pkg/errors:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ], diff --git a/cmd/kubeadm/app/phases/kubelet/config.go b/cmd/kubeadm/app/phases/kubelet/config.go index 4503d2ac99f..6e873528101 100644 --- a/cmd/kubeadm/app/phases/kubelet/config.go +++ b/cmd/kubeadm/app/phases/kubelet/config.go @@ -30,7 +30,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/version" clientset "k8s.io/client-go/kubernetes" - kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" @@ -38,9 +38,9 @@ import ( // WriteConfigToDisk writes the kubelet config object down to a file // Used at "kubeadm init" and "kubeadm upgrade" time -func WriteConfigToDisk(kubeletConfig *kubeletconfigv1beta1.KubeletConfiguration, kubeletDir string) error { +func WriteConfigToDisk(kubeletCfg kubeadmapi.ComponentConfig, kubeletDir string) error { - kubeletBytes, err := getConfigBytes(kubeletConfig) + kubeletBytes, err := kubeletCfg.Marshal() if err != nil { return err } @@ -49,9 +49,9 @@ func WriteConfigToDisk(kubeletConfig *kubeletconfigv1beta1.KubeletConfiguration, // CreateConfigMap creates a ConfigMap with the generic kubelet configuration. // Used at "kubeadm init" and "kubeadm upgrade" time -func CreateConfigMap(cfg *kubeletconfigv1beta1.KubeletConfiguration, k8sVersionStr string, client clientset.Interface) error { +func CreateConfigMap(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface) error { - k8sVersion, err := version.ParseSemantic(k8sVersionStr) + k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) if err != nil { return err } @@ -59,7 +59,12 @@ func CreateConfigMap(cfg *kubeletconfigv1beta1.KubeletConfiguration, k8sVersionS configMapName := kubeadmconstants.GetKubeletConfigMapName(k8sVersion) fmt.Printf("[kubelet] Creating a ConfigMap %q in namespace %s with the configuration for the kubelets in the cluster\n", configMapName, metav1.NamespaceSystem) - kubeletBytes, err := getConfigBytes(cfg) + kubeletCfg, ok := cfg.ComponentConfigs[componentconfigs.KubeletGroup] + if !ok { + return errors.New("no kubelet component config found in the active component config set") + } + + kubeletBytes, err := kubeletCfg.Marshal() if err != nil { return err } @@ -152,11 +157,6 @@ func configMapRBACName(k8sVersion *version.Version) string { return fmt.Sprintf("%s%d.%d", kubeadmconstants.KubeletBaseConfigMapRolePrefix, k8sVersion.Major(), k8sVersion.Minor()) } -// getConfigBytes marshals a KubeletConfiguration object to bytes -func getConfigBytes(kubeletConfig *kubeletconfigv1beta1.KubeletConfiguration) ([]byte, error) { - return componentconfigs.Known[componentconfigs.KubeletConfigurationKind].Marshal(kubeletConfig) -} - // writeConfigBytesToDisk writes a byte slice down to disk at the specific location of the kubelet config file func writeConfigBytesToDisk(b []byte, kubeletDir string) error { configFile := filepath.Join(kubeletDir, kubeadmconstants.KubeletConfigurationFileName) diff --git a/cmd/kubeadm/app/phases/kubelet/config_test.go b/cmd/kubeadm/app/phases/kubelet/config_test.go index 41e80eac990..569bb195e02 100644 --- a/cmd/kubeadm/app/phases/kubelet/config_test.go +++ b/cmd/kubeadm/app/phases/kubelet/config_test.go @@ -19,22 +19,20 @@ package kubelet import ( "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" - kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" + kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" "k8s.io/kubernetes/cmd/kubeadm/app/constants" + configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" ) func TestCreateConfigMap(t *testing.T) { nodeName := "fake-node" client := fake.NewSimpleClientset() - k8sVersionStr := constants.CurrentKubernetesVersion.String() - cfg := &kubeletconfigv1beta1.KubeletConfiguration{} - client.PrependReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) { return true, &v1.Node{ ObjectMeta: metav1.ObjectMeta{ @@ -53,7 +51,15 @@ func TestCreateConfigMap(t *testing.T) { return true, nil, nil }) - if err := CreateConfigMap(cfg, k8sVersionStr, client); err != nil { + clusterCfg := &kubeadmapiv1beta2.ClusterConfiguration{ + KubernetesVersion: constants.CurrentKubernetesVersion.String(), + } + internalcfg, err := configutil.DefaultedInitConfiguration(&kubeadmapiv1beta2.InitConfiguration{}, clusterCfg) + if err != nil { + t.Fatalf("unexpected failure by DefaultedInitConfiguration: %v", err) + } + + if err := CreateConfigMap(&internalcfg.ClusterConfiguration, client); err != nil { t.Errorf("CreateConfigMap: unexpected error %v", err) } } diff --git a/cmd/kubeadm/app/phases/upgrade/postupgrade.go b/cmd/kubeadm/app/phases/upgrade/postupgrade.go index ae9a0483f47..876c7995d23 100644 --- a/cmd/kubeadm/app/phases/upgrade/postupgrade.go +++ b/cmd/kubeadm/app/phases/upgrade/postupgrade.go @@ -53,7 +53,7 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitCon } // Create the new, version-branched kubelet ComponentConfig ConfigMap - if err := kubeletphase.CreateConfigMap(cfg.ClusterConfiguration.ComponentConfigs.Kubelet, cfg.KubernetesVersion, client); err != nil { + if err := kubeletphase.CreateConfigMap(&cfg.ClusterConfiguration, client); err != nil { errs = append(errs, errors.Wrap(err, "error creating kubelet configuration ConfigMap")) } diff --git a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go index 39da3b63e96..b1bef080a26 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go +++ b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go @@ -70,7 +70,7 @@ func UploadConfiguration(cfg *kubeadmapi.InitConfiguration, client clientset.Int // The components store their config in their own ConfigMaps, then reset the .ComponentConfig struct; // We don't want to mutate the cfg itself, so create a copy of it using .DeepCopy of it first clusterConfigurationToUpload := cfg.ClusterConfiguration.DeepCopy() - clusterConfigurationToUpload.ComponentConfigs = kubeadmapi.ComponentConfigs{} + clusterConfigurationToUpload.ComponentConfigs = kubeadmapi.ComponentConfigMap{} // Marshal the ClusterConfiguration into YAML clusterConfigurationYaml, err := configutil.MarshalKubeadmConfigObject(clusterConfigurationToUpload) diff --git a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go index 83db2f366c3..98e0e1c7230 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go +++ b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go @@ -87,13 +87,12 @@ func TestUploadConfiguration(t *testing.T) { } cfg, err := configutil.DefaultedInitConfiguration(initialcfg, clustercfg) - // cleans up component config to make cfg and decodedcfg comparable (now component config are not stored anymore in kubeadm-config config map) - cfg.ComponentConfigs = kubeadmapi.ComponentConfigs{} - if err != nil { t2.Fatalf("UploadConfiguration() error = %v", err) } + cfg.ComponentConfigs = kubeadmapi.ComponentConfigMap{} + status := &kubeadmapi.ClusterStatus{ APIEndpoints: map[string]kubeadmapi.APIEndpoint{ "node-foo": cfg.LocalAPIEndpoint, @@ -135,8 +134,15 @@ func TestUploadConfiguration(t *testing.T) { t2.Fatalf("unable to decode config from bytes: %v", err) } + if len(decodedCfg.ComponentConfigs) != 0 { + t2.Errorf("unexpected component configs in decodedCfg: %d", len(decodedCfg.ComponentConfigs)) + } + + // Force initialize with an empty map so that reflect.DeepEqual works + decodedCfg.ComponentConfigs = kubeadmapi.ComponentConfigMap{} + if !reflect.DeepEqual(decodedCfg, &cfg.ClusterConfiguration) { - t2.Errorf("the initial and decoded ClusterConfiguration didn't match") + t2.Errorf("the initial and decoded ClusterConfiguration didn't match:\n%t\n===\n%t", decodedCfg.ComponentConfigs == nil, cfg.ComponentConfigs == nil) } statusData := controlPlaneCfg.Data[kubeadmconstants.ClusterStatusConfigMapKey] diff --git a/cmd/kubeadm/app/util/config/BUILD b/cmd/kubeadm/app/util/config/BUILD index 584d183956f..30a18427776 100644 --- a/cmd/kubeadm/app/util/config/BUILD +++ b/cmd/kubeadm/app/util/config/BUILD @@ -57,6 +57,7 @@ go_test( "//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1beta1:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library", + "//cmd/kubeadm/app/componentconfigs:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", diff --git a/cmd/kubeadm/app/util/config/cluster.go b/cmd/kubeadm/app/util/config/cluster.go index 528973fa267..48bf7ce1b48 100644 --- a/cmd/kubeadm/app/util/config/cluster.go +++ b/cmd/kubeadm/app/util/config/cluster.go @@ -28,7 +28,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/version" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" certutil "k8s.io/client-go/util/cert" @@ -79,7 +78,7 @@ func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Inte } // gets the component configs from the corresponding config maps - if err := getComponentConfigs(client, &initcfg.ClusterConfiguration); err != nil { + if err := componentconfigs.FetchFromCluster(&initcfg.ClusterConfiguration, client); err != nil { return nil, errors.Wrap(err, "failed to get component configs") } @@ -193,28 +192,6 @@ func getAPIEndpoint(data map[string]string, nodeName string, apiEndpoint *kubead return nil } -// getComponentConfigs gets the component configs from the corresponding config maps -func getComponentConfigs(client clientset.Interface, clusterConfiguration *kubeadmapi.ClusterConfiguration) error { - // some config maps is versioned, so we need the KubernetesVersion for getting the right config map - k8sVersion := version.MustParseGeneric(clusterConfiguration.KubernetesVersion) - for kind, registration := range componentconfigs.Known { - obj, err := registration.GetFromConfigMap(client, k8sVersion) - if err != nil { - return err - } - - // Some components may not be installed or managed by kubeadm, hence GetFromConfigMap won't return an error or an object - if obj == nil { - continue - } - - if ok := registration.SetToInternalConfig(obj, clusterConfiguration); !ok { - return errors.Errorf("couldn't save componentconfig value for kind %q", string(kind)) - } - } - return nil -} - // GetClusterStatus returns the kubeadm cluster status read from the kubeadm-config ConfigMap func GetClusterStatus(client clientset.Interface) (*kubeadmapi.ClusterStatus, error) { configMap, err := apiclient.GetConfigMapWithRetry(client, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap) diff --git a/cmd/kubeadm/app/util/config/cluster_test.go b/cmd/kubeadm/app/util/config/cluster_test.go index 2d36d694e6b..eaf972e6474 100644 --- a/cmd/kubeadm/app/util/config/cluster_test.go +++ b/cmd/kubeadm/app/util/config/cluster_test.go @@ -23,12 +23,13 @@ import ( "strings" "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/version" clientset "k8s.io/client-go/kubernetes" clientsetfake "k8s.io/client-go/kubernetes/fake" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" ) @@ -404,88 +405,6 @@ func TestGetAPIEndpoint(t *testing.T) { } } -func TestGetComponentConfigs(t *testing.T) { - var tests = []struct { - name string - configMaps []fakeConfigMap - expectedError bool - }{ - { - name: "valid", - configMaps: []fakeConfigMap{ - { - name: kubeadmconstants.KubeProxyConfigMap, // Kube-proxy component config from corresponding ConfigMap. - data: map[string]string{ - kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]), - }, - }, - { - name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), // Kubelet component config from corresponding ConfigMap. - data: map[string]string{ - kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]), - }, - }, - }, - }, - { - name: "invalid - No kubelet component config ConfigMap", - configMaps: []fakeConfigMap{ - { - name: kubeadmconstants.KubeProxyConfigMap, - data: map[string]string{ - kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]), - }, - }, - }, - expectedError: true, - }, - { - name: "invalid - No kube-proxy component config ConfigMap", - configMaps: []fakeConfigMap{ - { - name: kubeadmconstants.GetKubeletConfigMapName(k8sVersion), - data: map[string]string{ - kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]), - }, - }, - }, - }, - } - - for _, rt := range tests { - t.Run(rt.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - - for _, c := range rt.configMaps { - err := c.create(client) - if err != nil { - t.Errorf("couldn't create ConfigMap %s", c.name) - return - } - } - - cfg := &kubeadmapi.InitConfiguration{ - ClusterConfiguration: kubeadmapi.ClusterConfiguration{ - KubernetesVersion: k8sVersionString, - }, - } - err := getComponentConfigs(client, &cfg.ClusterConfiguration) - if rt.expectedError != (err != nil) { - t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err) - return - } - if rt.expectedError { - return - } - - // Test expected values in InitConfiguration - if cfg.ComponentConfigs.Kubelet == nil { - t.Errorf("invalid cfg.ComponentConfigs.Kubelet") - } - }) - } -} - func TestGetInitConfigurationFromCluster(t *testing.T) { tmpdir, err := ioutil.TempDir("", "") if err != nil { @@ -685,11 +604,11 @@ func TestGetInitConfigurationFromCluster(t *testing.T) { if !rt.newControlPlane && (cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234) { t.Errorf("invalid cfg.LocalAPIEndpoint") } - if cfg.ComponentConfigs.Kubelet == nil { - t.Errorf("invalid cfg.ComponentConfigs.Kubelet") + if _, ok := cfg.ComponentConfigs[componentconfigs.KubeletGroup]; !ok { + t.Errorf("no cfg.ComponentConfigs[%q]", componentconfigs.KubeletGroup) } - if cfg.ComponentConfigs.KubeProxy == nil { - t.Errorf("invalid cfg.ComponentConfigs.KubeProxy") + if _, ok := cfg.ComponentConfigs[componentconfigs.KubeProxyGroup]; !ok { + t.Errorf("no cfg.ComponentConfigs[%q]", componentconfigs.KubeProxyGroup) } }) } diff --git a/cmd/kubeadm/app/util/config/initconfiguration.go b/cmd/kubeadm/app/util/config/initconfiguration.go index 56d9d782593..efeed3ba29e 100644 --- a/cmd/kubeadm/app/util/config/initconfiguration.go +++ b/cmd/kubeadm/app/util/config/initconfiguration.go @@ -53,7 +53,7 @@ func SetInitDynamicDefaults(cfg *kubeadmapi.InitConfiguration) error { if err := SetAPIEndpointDynamicDefaults(&cfg.LocalAPIEndpoint); err != nil { return err } - return SetClusterDynamicDefaults(&cfg.ClusterConfiguration, cfg.LocalAPIEndpoint.AdvertiseAddress, cfg.LocalAPIEndpoint.BindPort) + return SetClusterDynamicDefaults(&cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint) } // SetBootstrapTokensDynamicDefaults checks and sets configuration values for the BootstrapTokens object @@ -141,16 +141,9 @@ func SetAPIEndpointDynamicDefaults(cfg *kubeadmapi.APIEndpoint) error { } // SetClusterDynamicDefaults checks and sets values for the ClusterConfiguration object -func SetClusterDynamicDefaults(cfg *kubeadmapi.ClusterConfiguration, advertiseAddress string, bindPort int32) error { +func SetClusterDynamicDefaults(cfg *kubeadmapi.ClusterConfiguration, LocalAPIEndpoint *kubeadmapi.APIEndpoint) error { // Default all the embedded ComponentConfig structs - componentconfigs.Known.Default(cfg) - - ip := net.ParseIP(advertiseAddress) - if ip.To4() != nil { - cfg.ComponentConfigs.KubeProxy.BindAddress = kubeadmapiv1beta2.DefaultProxyBindAddressv4 - } else { - cfg.ComponentConfigs.KubeProxy.BindAddress = kubeadmapiv1beta2.DefaultProxyBindAddressv6 - } + componentconfigs.Default(cfg, LocalAPIEndpoint) // Resolve possible version labels and validate version string if err := NormalizeKubernetesVersion(cfg); err != nil { @@ -166,7 +159,7 @@ func SetClusterDynamicDefaults(cfg *kubeadmapi.ClusterConfiguration, advertiseAd return err } if port == "" { - cfg.ControlPlaneEndpoint = net.JoinHostPort(host, strconv.FormatInt(int64(bindPort), 10)) + cfg.ControlPlaneEndpoint = net.JoinHostPort(host, strconv.FormatInt(int64(LocalAPIEndpoint.BindPort), 10)) } } @@ -243,10 +236,9 @@ func BytesToInitConfiguration(b []byte) (*kubeadmapi.InitConfiguration, error) { } // documentMapToInitConfiguration converts a map of GVKs and YAML documents to defaulted and validated configuration object. -func documentMapToInitConfiguration(gvkmap map[schema.GroupVersionKind][]byte, allowDeprecated bool) (*kubeadmapi.InitConfiguration, error) { +func documentMapToInitConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated bool) (*kubeadmapi.InitConfiguration, error) { var initcfg *kubeadmapi.InitConfiguration var clustercfg *kubeadmapi.ClusterConfiguration - decodedComponentConfigObjects := map[componentconfigs.RegistrationKind]runtime.Object{} for gvk, fileContent := range gvkmap { // first, check if this GVK is supported and possibly not deprecated @@ -257,18 +249,6 @@ func documentMapToInitConfiguration(gvkmap map[schema.GroupVersionKind][]byte, a // verify the validity of the YAML strict.VerifyUnmarshalStrict(fileContent, gvk) - // Try to get the registration for the ComponentConfig based on the kind - regKind := componentconfigs.RegistrationKind(gvk.Kind) - if registration, found := componentconfigs.Known[regKind]; found { - // Unmarshal the bytes from the YAML document into a runtime.Object containing the ComponentConfiguration struct - obj, err := registration.Unmarshal(fileContent) - if err != nil { - return nil, err - } - decodedComponentConfigObjects[regKind] = obj - continue - } - if kubeadmutil.GroupVersionKindsHasInitConfiguration(gvk) { // Set initcfg to an empty struct value the deserializer will populate initcfg = &kubeadmapi.InitConfiguration{} @@ -313,16 +293,9 @@ func documentMapToInitConfiguration(gvkmap map[schema.GroupVersionKind][]byte, a initcfg.ClusterConfiguration = *clustercfg } - // Save the loaded ComponentConfig objects in the initcfg object - for kind, obj := range decodedComponentConfigObjects { - if registration, found := componentconfigs.Known[kind]; found { - if ok := registration.SetToInternalConfig(obj, &initcfg.ClusterConfiguration); !ok { - return nil, errors.Errorf("couldn't save componentconfig value for kind %q", string(kind)) - } - } else { - // This should never happen in practice - fmt.Printf("[config] WARNING: Decoded a kind that couldn't be saved to the internal configuration: %q\n", string(kind)) - } + // Load any component configs + if err := componentconfigs.FetchFromDocumentMap(&initcfg.ClusterConfiguration, gvkmap); err != nil { + return nil, err } // Applies dynamic defaults to settings not provided with flags diff --git a/cmd/kubeadm/app/util/config/joinconfiguration.go b/cmd/kubeadm/app/util/config/joinconfiguration.go index 5fe1facfdda..4f1c11a72e8 100644 --- a/cmd/kubeadm/app/util/config/joinconfiguration.go +++ b/cmd/kubeadm/app/util/config/joinconfiguration.go @@ -21,7 +21,6 @@ import ( "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" @@ -89,7 +88,7 @@ func LoadJoinConfigurationFromFile(cfgPath string) (*kubeadmapi.JoinConfiguratio // documentMapToJoinConfiguration takes a map between GVKs and YAML documents (as returned by SplitYAMLDocuments), // finds a JoinConfiguration, decodes it, dynamically defaults it and then validates it prior to return. -func documentMapToJoinConfiguration(gvkmap map[schema.GroupVersionKind][]byte, allowDeprecated bool) (*kubeadmapi.JoinConfiguration, error) { +func documentMapToJoinConfiguration(gvkmap kubeadmapi.DocumentMap, allowDeprecated bool) (*kubeadmapi.JoinConfiguration, error) { joinBytes := []byte{} for gvk, bytes := range gvkmap { // not interested in anything other than JoinConfiguration diff --git a/cmd/kubeadm/app/util/config/strict/BUILD b/cmd/kubeadm/app/util/config/strict/BUILD index 854d0226522..e0ef4b43c3a 100644 --- a/cmd/kubeadm/app/util/config/strict/BUILD +++ b/cmd/kubeadm/app/util/config/strict/BUILD @@ -22,7 +22,6 @@ go_test( embed = [":go_default_library"], deps = [ "//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library", - "//cmd/kubeadm/app/componentconfigs:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/kube-proxy/config/v1alpha1:go_default_library", diff --git a/cmd/kubeadm/app/util/config/strict/strict_test.go b/cmd/kubeadm/app/util/config/strict/strict_test.go index fd832dcd5d6..c2ebaef3d6c 100644 --- a/cmd/kubeadm/app/util/config/strict/strict_test.go +++ b/cmd/kubeadm/app/util/config/strict/strict_test.go @@ -25,7 +25,6 @@ import ( kubeproxyconfigv1alpha1 "k8s.io/kube-proxy/config/v1alpha1" kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" - "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs" "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) @@ -55,13 +54,13 @@ func TestVerifyUnmarshalStrict(t *testing.T) { }, { fileName: "invalid_duplicate_field_kubeletcfg.yaml", - kind: string(componentconfigs.KubeletConfigurationKind), + kind: "KubeletConfiguration", groupVersion: kubeletconfigv1beta1.SchemeGroupVersion, expectedError: true, }, { fileName: "invalid_duplicate_field_kubeproxycfg.yaml", - kind: string(componentconfigs.KubeProxyConfigurationKind), + kind: "KubeProxyConfiguration", groupVersion: kubeproxyconfigv1alpha1.SchemeGroupVersion, expectedError: true, }, @@ -85,13 +84,13 @@ func TestVerifyUnmarshalStrict(t *testing.T) { }, { fileName: "invalid_unknown_field_kubeletcfg.yaml", - kind: string(componentconfigs.KubeletConfigurationKind), + kind: "KubeletConfiguration", groupVersion: kubeletconfigv1beta1.SchemeGroupVersion, expectedError: true, }, { fileName: "invalid_unknown_field_kubeproxycfg.yaml", - kind: string(componentconfigs.KubeProxyConfigurationKind), + kind: "KubeProxyConfiguration", groupVersion: kubeproxyconfigv1alpha1.SchemeGroupVersion, expectedError: true, }, @@ -129,13 +128,13 @@ func TestVerifyUnmarshalStrict(t *testing.T) { }, { fileName: "valid_kubeletcfg.yaml", - kind: string(componentconfigs.KubeletConfigurationKind), + kind: "KubeletConfiguration", groupVersion: kubeletconfigv1beta1.SchemeGroupVersion, expectedError: false, }, { fileName: "valid_kubeproxycfg.yaml", - kind: string(componentconfigs.KubeProxyConfigurationKind), + kind: "KubeProxyConfiguration", groupVersion: kubeproxyconfigv1alpha1.SchemeGroupVersion, expectedError: false, }, diff --git a/cmd/kubeadm/app/util/marshal.go b/cmd/kubeadm/app/util/marshal.go index de890f64ca6..95a97013f9b 100644 --- a/cmd/kubeadm/app/util/marshal.go +++ b/cmd/kubeadm/app/util/marshal.go @@ -30,6 +30,7 @@ import ( errorsutil "k8s.io/apimachinery/pkg/util/errors" utilyaml "k8s.io/apimachinery/pkg/util/yaml" clientsetscheme "k8s.io/client-go/kubernetes/scheme" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) @@ -73,8 +74,8 @@ func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs se // SplitYAMLDocuments reads the YAML bytes per-document, unmarshals the TypeMeta information from each document // and returns a map between the GroupVersionKind of the document and the document bytes -func SplitYAMLDocuments(yamlBytes []byte) (map[schema.GroupVersionKind][]byte, error) { - gvkmap := map[schema.GroupVersionKind][]byte{} +func SplitYAMLDocuments(yamlBytes []byte) (kubeadmapi.DocumentMap, error) { + gvkmap := kubeadmapi.DocumentMap{} knownKinds := map[string]bool{} errs := []error{} buf := bytes.NewBuffer(yamlBytes) diff --git a/cmd/kubeadm/app/util/marshal_test.go b/cmd/kubeadm/app/util/marshal_test.go index 86fac021fff..7b3f58c2728 100644 --- a/cmd/kubeadm/app/util/marshal_test.go +++ b/cmd/kubeadm/app/util/marshal_test.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2" "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) @@ -159,20 +160,20 @@ func TestSplitYAMLDocuments(t *testing.T) { var tests = []struct { name string fileContents []byte - gvkmap map[schema.GroupVersionKind][]byte + gvkmap kubeadmapi.DocumentMap expectedErr bool }{ { name: "FooOnly", fileContents: files["foo"], - gvkmap: map[schema.GroupVersionKind][]byte{ + gvkmap: kubeadmapi.DocumentMap{ {Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: files["foo"], }, }, { name: "FooBar", fileContents: bytes.Join([][]byte{files["foo"], files["bar"]}, []byte(constants.YAMLDocumentSeparator)), - gvkmap: map[schema.GroupVersionKind][]byte{ + gvkmap: kubeadmapi.DocumentMap{ {Group: "foo.k8s.io", Version: "v1", Kind: "Foo"}: files["foo"], {Group: "bar.k8s.io", Version: "v2", Kind: "Bar"}: files["bar"], }, diff --git a/cmd/kubeadm/test/cmd/init_test.go b/cmd/kubeadm/test/cmd/init_test.go index 4aab86c195d..dd3f4250f78 100644 --- a/cmd/kubeadm/test/cmd/init_test.go +++ b/cmd/kubeadm/test/cmd/init_test.go @@ -180,6 +180,16 @@ func TestCmdInitConfig(t *testing.T) { args: "--kubernetes-version=1.11.0 --config=testdata/init/v1beta2.yaml", expected: false, }, + { + name: "can load current component config", + args: "--config=testdata/init/current-component-config.yaml", + expected: true, + }, + { + name: "can't load old component config", + args: "--config=testdata/init/old-component-config.yaml", + expected: false, + }, } for _, rt := range initTest { diff --git a/cmd/kubeadm/test/cmd/testdata/init/current-component-config.yaml b/cmd/kubeadm/test/cmd/testdata/init/current-component-config.yaml new file mode 100644 index 00000000000..a3e15b92b0d --- /dev/null +++ b/cmd/kubeadm/test/cmd/testdata/init/current-component-config.yaml @@ -0,0 +1,5 @@ +apiVersion: kubeadm.k8s.io/v1beta2 +kind: ClusterConfiguration +--- +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration diff --git a/cmd/kubeadm/test/cmd/testdata/init/old-component-config.yaml b/cmd/kubeadm/test/cmd/testdata/init/old-component-config.yaml new file mode 100644 index 00000000000..5334ce9f2b0 --- /dev/null +++ b/cmd/kubeadm/test/cmd/testdata/init/old-component-config.yaml @@ -0,0 +1,5 @@ +apiVersion: kubeadm.k8s.io/v1beta2 +kind: ClusterConfiguration +--- +apiVersion: kubelet.config.k8s.io/v1alpha1 +kind: KubeletConfiguration