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 <rostislavg@vmware.com>
This commit is contained in:
Rostislav M. Georgiev 2019-11-15 17:11:08 +02:00
parent 41757d673e
commit b881f19c8b
47 changed files with 1750 additions and 942 deletions

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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",

View File

@ -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)
}
if in.KubeProxy != nil {
in, out := &in.KubeProxy, &out.KubeProxy
*out = new(v1alpha1.KubeProxyConfiguration)
(*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
}
}
// 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.

View File

@ -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",

View File

@ -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"},

View File

@ -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,

View File

@ -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",

View File

@ -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")
}

View File

@ -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")
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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,
})
}

View File

@ -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{}
}

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}
}

View File

@ -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{})
})
}

View File

@ -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
}

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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")
}

View File

@ -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)
}
})
}
}

View File

@ -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",
],

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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"))
}

View File

@ -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)

View File

@ -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]

View File

@ -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",

View File

@ -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)

View File

@ -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)
}
})
}

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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,
},

View File

@ -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)

View File

@ -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"],
},

View File

@ -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 {

View File

@ -0,0 +1,5 @@
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

View File

@ -0,0 +1,5 @@
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
---
apiVersion: kubelet.config.k8s.io/v1alpha1
kind: KubeletConfiguration