mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
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:
parent
41757d673e
commit
b881f19c8b
@ -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",
|
||||
],
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
|
@ -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",
|
||||
|
@ -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"},
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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,
|
||||
})
|
||||
}
|
204
cmd/kubeadm/app/componentconfigs/configset.go
Normal file
204
cmd/kubeadm/app/componentconfigs/configset.go
Normal 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{}
|
||||
}
|
111
cmd/kubeadm/app/componentconfigs/configset_test.go
Normal file
111
cmd/kubeadm/app/componentconfigs/configset_test.go
Normal 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))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
176
cmd/kubeadm/app/componentconfigs/kubelet.go
Normal file
176
cmd/kubeadm/app/componentconfigs/kubelet.go
Normal 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
|
||||
}
|
488
cmd/kubeadm/app/componentconfigs/kubelet_test.go
Normal file
488
cmd/kubeadm/app/componentconfigs/kubelet_test.go
Normal 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)
|
||||
})
|
||||
}
|
114
cmd/kubeadm/app/componentconfigs/kubeproxy.go
Normal file
114
cmd/kubeadm/app/componentconfigs/kubeproxy.go
Normal 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
|
||||
}
|
||||
}
|
440
cmd/kubeadm/app/componentconfigs/kubeproxy_test.go
Normal file
440
cmd/kubeadm/app/componentconfigs/kubeproxy_test.go
Normal 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{})
|
||||
})
|
||||
}
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
],
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"))
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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)
|
||||
|
@ -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"],
|
||||
},
|
||||
|
@ -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 {
|
||||
|
5
cmd/kubeadm/test/cmd/testdata/init/current-component-config.yaml
vendored
Normal file
5
cmd/kubeadm/test/cmd/testdata/init/current-component-config.yaml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
kind: ClusterConfiguration
|
||||
---
|
||||
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||
kind: KubeletConfiguration
|
5
cmd/kubeadm/test/cmd/testdata/init/old-component-config.yaml
vendored
Normal file
5
cmd/kubeadm/test/cmd/testdata/init/old-component-config.yaml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
kind: ClusterConfiguration
|
||||
---
|
||||
apiVersion: kubelet.config.k8s.io/v1alpha1
|
||||
kind: KubeletConfiguration
|
Loading…
Reference in New Issue
Block a user