diff --git a/cmd/kubeadm/app/BUILD b/cmd/kubeadm/app/BUILD index e7eb023527c..a2366dc98c1 100644 --- a/cmd/kubeadm/app/BUILD +++ b/cmd/kubeadm/app/BUILD @@ -41,6 +41,7 @@ filegroup( "//cmd/kubeadm/app/phases/controlplane:all-srcs", "//cmd/kubeadm/app/phases/etcd:all-srcs", "//cmd/kubeadm/app/phases/kubeconfig:all-srcs", + "//cmd/kubeadm/app/phases/kubelet:all-srcs", "//cmd/kubeadm/app/phases/markmaster:all-srcs", "//cmd/kubeadm/app/phases/selfhosting:all-srcs", "//cmd/kubeadm/app/phases/token:all-srcs", diff --git a/cmd/kubeadm/app/apis/kubeadm/BUILD b/cmd/kubeadm/app/apis/kubeadm/BUILD index d35075b2799..b1516565a46 100644 --- a/cmd/kubeadm/app/apis/kubeadm/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/BUILD @@ -16,6 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm", deps = [ + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/BUILD b/cmd/kubeadm/app/apis/kubeadm/fuzzer/BUILD index 5964d9097e9..52582b01b69 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/BUILD @@ -11,6 +11,8 @@ go_library( importpath = "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/fuzzer", deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//pkg/util/pointer:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go index fbf0090c265..dcf554fbabb 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go @@ -24,6 +24,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" ) // Funcs returns the fuzzer functions for the kubeadm apis. @@ -39,20 +41,41 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { obj.Networking.DNSDomain = "foo" obj.AuthorizationModes = []string{"foo"} obj.CertificatesDir = "foo" - obj.APIServerCertSANs = []string{} + obj.APIServerCertSANs = []string{"foo"} obj.Token = "foo" obj.Etcd.Image = "foo" obj.Etcd.DataDir = "foo" obj.ImageRepository = "foo" obj.CIImageRepository = "" obj.UnifiedControlPlaneImage = "foo" - obj.FeatureGates = map[string]bool{} + obj.FeatureGates = map[string]bool{"foo": true} + obj.APIServerExtraArgs = map[string]string{"foo": "foo"} + obj.APIServerExtraVolumes = []kubeadm.HostPathMount{{ + Name: "foo", + HostPath: "foo", + MountPath: "foo", + }} + obj.Etcd.ExtraArgs = map[string]string{"foo": "foo"} obj.Etcd.SelfHosted = &kubeadm.SelfHostedEtcd{ CertificatesDir: "/etc/kubernetes/pki/etcd", ClusterServiceName: "etcd-cluster", EtcdVersion: "v0.1.0", OperatorVersion: "v0.1.0", } + obj.KubeletConfiguration = kubeadm.KubeletConfiguration{ + BaseConfig: &kubeletconfigv1alpha1.KubeletConfiguration{ + PodManifestPath: "foo", + AllowPrivileged: utilpointer.BoolPtr(true), + ClusterDNS: []string{"foo"}, + ClusterDomain: "foo", + Authorization: kubeletconfigv1alpha1.KubeletAuthorization{Mode: "foo"}, + Authentication: kubeletconfigv1alpha1.KubeletAuthentication{ + X509: kubeletconfigv1alpha1.KubeletX509Authentication{ClientCAFile: "foo"}, + }, + CAdvisorPort: utilpointer.Int32Ptr(0), + }, + } + kubeletconfigv1alpha1.SetDefaults_KubeletConfiguration(obj.KubeletConfiguration.BaseConfig) }, func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) { c.FuzzNoCustom(obj) diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index 72f6d2b3438..b02c4f55dd6 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -18,6 +18,7 @@ package kubeadm import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -27,13 +28,14 @@ import ( type MasterConfiguration struct { metav1.TypeMeta - API API - Etcd Etcd - Networking Networking - KubernetesVersion string - CloudProvider string - NodeName string - AuthorizationModes []string + API API + Etcd Etcd + KubeletConfiguration KubeletConfiguration + Networking Networking + KubernetesVersion string + CloudProvider string + NodeName string + AuthorizationModes []string Token string TokenTTL *metav1.Duration @@ -142,6 +144,14 @@ type NodeConfiguration struct { // without CA verification via DiscoveryTokenCACertHashes. This can weaken // the security of kubeadm since other nodes can impersonate the master. DiscoveryTokenUnsafeSkipCAVerification bool + + // FeatureGates enabled by the user + FeatureGates map[string]bool +} + +// KubeletConfiguration contains elements describing initial remote configuration of kubelet +type KubeletConfiguration struct { + BaseConfig *kubeletconfigv1alpha1.KubeletConfiguration } // GetControlPlaneImageRepository returns name of image repository diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD index 5600981190f..19172f675f2 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/BUILD @@ -26,6 +26,8 @@ go_library( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//pkg/util/pointer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go index 6edb7785fb5..19887296b4b 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go @@ -23,6 +23,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" ) const ( @@ -30,6 +32,8 @@ const ( DefaultServiceDNSDomain = "cluster.local" // DefaultServicesSubnet defines default service subnet range DefaultServicesSubnet = "10.96.0.0/12" + // DefaultClusterDNSIP defines default DNS IP + DefaultClusterDNSIP = "10.96.0.10" // DefaultKubernetesVersion defines default kubernetes version DefaultKubernetesVersion = "stable-1.8" // DefaultAPIBindPort defines default API port @@ -40,6 +44,8 @@ const ( DefaultCertificatesDir = "/etc/kubernetes/pki" // DefaultImageRepository defines default image registry DefaultImageRepository = "gcr.io/google_containers" + // DefaultManifestsDir defines default manifests directory + DefaultManifestsDir = "/etc/kubernetes/manifests" // DefaultEtcdDataDir defines default location of etcd where static pods will save data to DefaultEtcdDataDir = "/var/lib/etcd" @@ -98,6 +104,7 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { } SetDefaultsEtcdSelfHosted(obj) + SetDefaults_KubeletConfiguration(obj) } // SetDefaults_NodeConfiguration assigns default values to a regular node @@ -142,3 +149,31 @@ func SetDefaultsEtcdSelfHosted(obj *MasterConfiguration) { obj.Etcd.SelfHosted.CertificatesDir = DefaultEtcdCertDir } } + +// SetDefaults_KubeletConfiguration assigns default values to kubelet +func SetDefaults_KubeletConfiguration(obj *MasterConfiguration) { + if obj.KubeletConfiguration.BaseConfig == nil { + obj.KubeletConfiguration.BaseConfig = &kubeletconfigv1alpha1.KubeletConfiguration{} + } + if obj.KubeletConfiguration.BaseConfig.PodManifestPath == "" { + obj.KubeletConfiguration.BaseConfig.PodManifestPath = DefaultManifestsDir + } + if obj.KubeletConfiguration.BaseConfig.AllowPrivileged == nil { + obj.KubeletConfiguration.BaseConfig.AllowPrivileged = utilpointer.BoolPtr(true) + } + if obj.KubeletConfiguration.BaseConfig.ClusterDNS == nil { + obj.KubeletConfiguration.BaseConfig.ClusterDNS = []string{DefaultClusterDNSIP} + } + if obj.KubeletConfiguration.BaseConfig.ClusterDomain == "" { + obj.KubeletConfiguration.BaseConfig.ClusterDomain = DefaultServiceDNSDomain + } + if obj.KubeletConfiguration.BaseConfig.Authorization.Mode == "" { + obj.KubeletConfiguration.BaseConfig.Authorization.Mode = kubeletconfigv1alpha1.KubeletAuthorizationModeWebhook + } + if obj.KubeletConfiguration.BaseConfig.Authentication.X509.ClientCAFile == "" { + obj.KubeletConfiguration.BaseConfig.Authentication.X509.ClientCAFile = DefaultCACertPath + } + if obj.KubeletConfiguration.BaseConfig.CAdvisorPort == nil { + obj.KubeletConfiguration.BaseConfig.CAdvisorPort = utilpointer.Int32Ptr(0) + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go index b8e8f09fb30..06ea582386d 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go @@ -18,6 +18,7 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -27,13 +28,14 @@ import ( type MasterConfiguration struct { metav1.TypeMeta `json:",inline"` - API API `json:"api"` - Etcd Etcd `json:"etcd"` - Networking Networking `json:"networking"` - KubernetesVersion string `json:"kubernetesVersion"` - CloudProvider string `json:"cloudProvider"` - NodeName string `json:"nodeName"` - AuthorizationModes []string `json:"authorizationModes,omitempty"` + API API `json:"api"` + Etcd Etcd `json:"etcd"` + KubeletConfiguration KubeletConfiguration `json:"kubeletConfiguration"` + Networking Networking `json:"networking"` + KubernetesVersion string `json:"kubernetesVersion"` + CloudProvider string `json:"cloudProvider"` + NodeName string `json:"nodeName"` + AuthorizationModes []string `json:"authorizationModes,omitempty"` Token string `json:"token"` TokenTTL *metav1.Duration `json:"tokenTTL,omitempty"` @@ -136,6 +138,14 @@ type NodeConfiguration struct { // without CA verification via DiscoveryTokenCACertHashes. This can weaken // the security of kubeadm since other nodes can impersonate the master. DiscoveryTokenUnsafeSkipCAVerification bool `json:"discoveryTokenUnsafeSkipCAVerification"` + + // FeatureGates enabled by the user + FeatureGates map[string]bool `json:"featureGates,omitempty"` +} + +// KubeletConfiguration contains elements describing initial remote configuration of kubelet +type KubeletConfiguration struct { + BaseConfig *kubeletconfigv1alpha1.KubeletConfiguration `json:"baseConfig"` } // HostPathMount contains elements describing volumes that are mounted from the diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go index e61368cdbc8..13d39b25874 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go @@ -25,6 +25,7 @@ import ( conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeletconfig_v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" unsafe "unsafe" ) @@ -42,6 +43,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { Convert_kubeadm_Etcd_To_v1alpha1_Etcd, Convert_v1alpha1_HostPathMount_To_kubeadm_HostPathMount, Convert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount, + Convert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration, + Convert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration, Convert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration, Convert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration, Convert_v1alpha1_Networking_To_kubeadm_Networking, @@ -135,6 +138,26 @@ func Convert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount(in *kubeadm.HostPat return autoConvert_kubeadm_HostPathMount_To_v1alpha1_HostPathMount(in, out, s) } +func autoConvert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in *KubeletConfiguration, out *kubeadm.KubeletConfiguration, s conversion.Scope) error { + out.BaseConfig = (*kubeletconfig_v1alpha1.KubeletConfiguration)(unsafe.Pointer(in.BaseConfig)) + return nil +} + +// Convert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in *KubeletConfiguration, out *kubeadm.KubeletConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration(in, out, s) +} + +func autoConvert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration(in *kubeadm.KubeletConfiguration, out *KubeletConfiguration, s conversion.Scope) error { + out.BaseConfig = (*kubeletconfig_v1alpha1.KubeletConfiguration)(unsafe.Pointer(in.BaseConfig)) + return nil +} + +// Convert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration is an autogenerated conversion function. +func Convert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration(in *kubeadm.KubeletConfiguration, out *KubeletConfiguration, s conversion.Scope) error { + return autoConvert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration(in, out, s) +} + func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in *MasterConfiguration, out *kubeadm.MasterConfiguration, s conversion.Scope) error { if err := Convert_v1alpha1_API_To_kubeadm_API(&in.API, &out.API, s); err != nil { return err @@ -142,6 +165,9 @@ func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in if err := Convert_v1alpha1_Etcd_To_kubeadm_Etcd(&in.Etcd, &out.Etcd, s); err != nil { return err } + if err := Convert_v1alpha1_KubeletConfiguration_To_kubeadm_KubeletConfiguration(&in.KubeletConfiguration, &out.KubeletConfiguration, s); err != nil { + return err + } if err := Convert_v1alpha1_Networking_To_kubeadm_Networking(&in.Networking, &out.Networking, s); err != nil { return err } @@ -177,6 +203,9 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in if err := Convert_kubeadm_Etcd_To_v1alpha1_Etcd(&in.Etcd, &out.Etcd, s); err != nil { return err } + if err := Convert_kubeadm_KubeletConfiguration_To_v1alpha1_KubeletConfiguration(&in.KubeletConfiguration, &out.KubeletConfiguration, s); err != nil { + return err + } if err := Convert_kubeadm_Networking_To_v1alpha1_Networking(&in.Networking, &out.Networking, s); err != nil { return err } @@ -240,6 +269,7 @@ func autoConvert_v1alpha1_NodeConfiguration_To_kubeadm_NodeConfiguration(in *Nod out.Token = in.Token out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes)) out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification + out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) return nil } @@ -258,6 +288,7 @@ func autoConvert_kubeadm_NodeConfiguration_To_v1alpha1_NodeConfiguration(in *kub out.Token = in.Token out.DiscoveryTokenCACertHashes = *(*[]string)(unsafe.Pointer(&in.DiscoveryTokenCACertHashes)) out.DiscoveryTokenUnsafeSkipCAVerification = in.DiscoveryTokenUnsafeSkipCAVerification + out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) return nil } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go index 21c00705b6a..1fbc3767d13 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go @@ -23,6 +23,7 @@ package v1alpha1 import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + kubeletconfig_v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -94,12 +95,38 @@ func (in *HostPathMount) DeepCopy() *HostPathMount { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { + *out = *in + if in.BaseConfig != nil { + in, out := &in.BaseConfig, &out.BaseConfig + if *in == nil { + *out = nil + } else { + *out = new(kubeletconfig_v1alpha1.KubeletConfiguration) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeletConfiguration. +func (in *KubeletConfiguration) DeepCopy() *KubeletConfiguration { + if in == nil { + return nil + } + out := new(KubeletConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { *out = *in out.TypeMeta = in.TypeMeta out.API = in.API in.Etcd.DeepCopyInto(&out.Etcd) + in.KubeletConfiguration.DeepCopyInto(&out.KubeletConfiguration) out.Networking = in.Networking if in.AuthorizationModes != nil { in, out := &in.AuthorizationModes, &out.AuthorizationModes @@ -215,6 +242,13 @@ func (in *NodeConfiguration) DeepCopyInto(out *NodeConfiguration) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.FeatureGates != nil { + in, out := &in.FeatureGates, &out.FeatureGates + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } return } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.defaults.go index 47f1f439d4b..6029af668dc 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.defaults.go @@ -22,6 +22,7 @@ package v1alpha1 import ( runtime "k8s.io/apimachinery/pkg/runtime" + kubeletconfig_v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" ) // RegisterDefaults adds defaulters functions to the given scheme. @@ -35,6 +36,9 @@ func RegisterDefaults(scheme *runtime.Scheme) error { func SetObjectDefaults_MasterConfiguration(in *MasterConfiguration) { SetDefaults_MasterConfiguration(in) + if in.KubeletConfiguration.BaseConfig != nil { + kubeletconfig_v1alpha1.SetDefaults_KubeletConfiguration(in.KubeletConfiguration.BaseConfig) + } } func SetObjectDefaults_NodeConfiguration(in *NodeConfiguration) { diff --git a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go index 122569fefed..3a50f14d66f 100644 --- a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go @@ -23,6 +23,7 @@ package kubeadm import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -94,12 +95,38 @@ func (in *HostPathMount) DeepCopy() *HostPathMount { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) { + *out = *in + if in.BaseConfig != nil { + in, out := &in.BaseConfig, &out.BaseConfig + if *in == nil { + *out = nil + } else { + *out = new(v1alpha1.KubeletConfiguration) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeletConfiguration. +func (in *KubeletConfiguration) DeepCopy() *KubeletConfiguration { + if in == nil { + return nil + } + out := new(KubeletConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { *out = *in out.TypeMeta = in.TypeMeta out.API = in.API in.Etcd.DeepCopyInto(&out.Etcd) + in.KubeletConfiguration.DeepCopyInto(&out.KubeletConfiguration) out.Networking = in.Networking if in.AuthorizationModes != nil { in, out := &in.AuthorizationModes, &out.AuthorizationModes @@ -215,6 +242,13 @@ func (in *NodeConfiguration) DeepCopyInto(out *NodeConfiguration) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.FeatureGates != nil { + in, out := &in.FeatureGates, &out.FeatureGates + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } return } diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 6d0dabf8e09..fb3f2d7a92b 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -40,6 +40,7 @@ go_library( "//cmd/kubeadm/app/phases/controlplane:go_default_library", "//cmd/kubeadm/app/phases/etcd:go_default_library", "//cmd/kubeadm/app/phases/kubeconfig:go_default_library", + "//cmd/kubeadm/app/phases/kubelet:go_default_library", "//cmd/kubeadm/app/phases/markmaster:go_default_library", "//cmd/kubeadm/app/phases/selfhosting:go_default_library", "//cmd/kubeadm/app/phases/uploadconfig:go_default_library", diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 7b58ece280a..815a7ce81c3 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -47,6 +47,7 @@ import ( controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" + kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster" selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" uploadconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" @@ -217,7 +218,6 @@ func AddInitOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipPreFlight, sk // NewInit validates given arguments and instantiates Init struct with provided information. func NewInit(cfgPath string, cfg *kubeadmapi.MasterConfiguration, skipPreFlight, skipTokenPrint, dryRun bool, criSocket string) (*Init, error) { - fmt.Println("[kubeadm] WARNING: kubeadm is currently in beta") if cfgPath != "" { @@ -354,6 +354,14 @@ func (i *Init) Run(out io.Writer) error { return fmt.Errorf("couldn't initialize a Kubernetes cluster") } + // NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf + if features.Enabled(i.cfg.FeatureGates, features.DynamicKubeletConfig) { + // Create base kubelet configuration for dynamic kubelet configuration feature. + if err := kubeletphase.CreateBaseKubeletConfiguration(i.cfg, client); err != nil { + return fmt.Errorf("error uploading configuration: %v", err) + } + } + // Upload currently used configuration to the cluster // Note: This is done right in the beginning of cluster initialization; as we might want to make other phases // depend on centralized information from this source in the future diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index 0d84aea2382..5ec77b15e27 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -21,6 +21,7 @@ import ( "io" "io/ioutil" "path/filepath" + "strings" "github.com/renstrom/dedent" "github.com/spf13/cobra" @@ -33,6 +34,8 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/discovery" + "k8s.io/kubernetes/cmd/kubeadm/app/features" + kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" @@ -103,6 +106,7 @@ func NewCmdJoin(out io.Writer) *cobra.Command { var skipPreFlight bool var cfgPath string var criSocket string + var featureGatesString string cmd := &cobra.Command{ Use: "join [flags]", @@ -111,6 +115,11 @@ func NewCmdJoin(out io.Writer) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { cfg.DiscoveryTokenAPIServers = args + var err error + if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil { + kubeadmutil.CheckErr(err) + } + legacyscheme.Scheme.Default(cfg) internalcfg := &kubeadmapi.NodeConfiguration{} legacyscheme.Scheme.Convert(cfg, internalcfg, nil) @@ -122,14 +131,14 @@ func NewCmdJoin(out io.Writer) *cobra.Command { }, } - AddJoinConfigFlags(cmd.PersistentFlags(), cfg) + AddJoinConfigFlags(cmd.PersistentFlags(), cfg, &featureGatesString) AddJoinOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipPreFlight, &criSocket) return cmd } // AddJoinConfigFlags adds join flags bound to the config to the specified flagset -func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfiguration) { +func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfiguration, featureGatesString *string) { flagSet.StringVar( &cfg.DiscoveryFile, "discovery-file", "", "A file or url from which to load cluster information.") @@ -151,6 +160,10 @@ func AddJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiext.NodeConfigurat flagSet.StringVar( &cfg.Token, "token", "", "Use this token for both discovery-token and tls-bootstrap-token.") + flagSet.StringVar( + featureGatesString, "feature-gates", *featureGatesString, + "A set of key=value pairs that describe feature gates for various features. "+ + "Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n")) } // AddJoinOtherFlags adds join flags that are not bound to a configuration file to the given flagset @@ -224,6 +237,19 @@ func (j *Join) Run(out io.Writer) error { return err } + // NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf + if features.Enabled(j.cfg.FeatureGates, features.DynamicKubeletConfig) { + client, err := kubeconfigutil.ClientSetFromFile(kubeadmconstants.GetAdminKubeConfigPath()) + if err != nil { + return err + } + + // Update the node with remote base kubelet configuration + if err := kubeletphase.UpdateNodeWithConfigMap(client, j.cfg.NodeName); err != nil { + return err + } + } + kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletBootstrapKubeConfigFileName) // Write the bootstrap kubelet config file or the TLS-Boostrapped kubelet config file down to disk diff --git a/cmd/kubeadm/app/cmd/upgrade/common_test.go b/cmd/kubeadm/app/cmd/upgrade/common_test.go index ce00399b2cb..6074c03e2ff 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/common_test.go @@ -51,6 +51,8 @@ func TestPrintConfiguration(t *testing.T) { image: "" keyFile: "" imageRepository: "" + kubeletConfiguration: + baseConfig: null kubernetesVersion: v1.7.1 networking: dnsDomain: "" @@ -82,6 +84,8 @@ func TestPrintConfiguration(t *testing.T) { image: "" keyFile: "" imageRepository: "" + kubeletConfiguration: + baseConfig: null kubernetesVersion: v1.7.1 networking: dnsDomain: "" @@ -123,6 +127,8 @@ func TestPrintConfiguration(t *testing.T) { etcdVersion: v0.1.0 operatorVersion: v0.1.0 imageRepository: "" + kubeletConfiguration: + baseConfig: null kubernetesVersion: v1.7.1 networking: dnsDomain: "" diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index ab0bb8669f6..c465d8aa36e 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -115,12 +115,17 @@ const ( // system:nodes group subject is removed if present. NodesClusterRoleBinding = "system:node" + // KubeletBaseConfigMapRoleName defines the base kubelet configuration ConfigMap. + KubeletBaseConfigMapRoleName = "kubeadm:kubelet-base-configmap" + // APICallRetryInterval defines how long kubeadm should wait before retrying a failed API operation APICallRetryInterval = 500 * time.Millisecond // DiscoveryRetryInterval specifies how long kubeadm should wait before retrying to connect to the master when doing discovery DiscoveryRetryInterval = 5 * time.Second // MarkMasterTimeout specifies how long kubeadm should wait for applying the label and taint on the master before timing out MarkMasterTimeout = 2 * time.Minute + // UpdateNodeTimeout specifies how long kubeadm should wait for updating node with the initial remote configuration of kubelet before timing out + UpdateNodeTimeout = 2 * time.Minute // MinimumAddressesInServiceSubnet defines minimum amount of nodes the Service subnet should allow. // We need at least ten, because the DNS service is always at the tenth cluster clusterIP @@ -140,6 +145,14 @@ const ( // MasterConfigurationConfigMapKey specifies in what ConfigMap key the master configuration should be stored MasterConfigurationConfigMapKey = "MasterConfiguration" + // KubeletBaseConfigurationConfigMap specifies in what ConfigMap in the kube-system namespace the initial remote configuration of kubelet should be stored + KubeletBaseConfigurationConfigMap = "kubelet-base-config-1.9" + + // KubeletBaseConfigurationConfigMapKey specifies in what ConfigMap key the initial remote configuration of kubelet should be stored + // TODO: Use the constant ("kubelet.config.k8s.io") defined in pkg/kubelet/kubeletconfig/util/keys/keys.go + // after https://github.com/kubernetes/kubernetes/pull/53833 being merged. + KubeletBaseConfigurationConfigMapKey = "kubelet" + // MinExternalEtcdVersion indicates minimum external etcd version which kubeadm supports MinExternalEtcdVersion = "3.0.14" diff --git a/cmd/kubeadm/app/features/features.go b/cmd/kubeadm/app/features/features.go index 969033f68e9..64c62a07ea6 100644 --- a/cmd/kubeadm/app/features/features.go +++ b/cmd/kubeadm/app/features/features.go @@ -41,6 +41,9 @@ const ( // SupportIPVSProxyMode is alpha in v1.8 SupportIPVSProxyMode = "SupportIPVSProxyMode" + + // DynamicKubeletConfig is alpha in v1.9 + DynamicKubeletConfig = "DynamicKubeletConfig" ) var v190 = version.MustParseSemantic("v1.9.0-alpha.1") @@ -52,6 +55,7 @@ var InitFeatureGates = FeatureList{ HighAvailability: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, SupportIPVSProxyMode: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, CoreDNS: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, + DynamicKubeletConfig: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, } // Feature represents a feature being gated diff --git a/cmd/kubeadm/app/phases/controlplane/manifests.go b/cmd/kubeadm/app/phases/controlplane/manifests.go index 28cc40bd526..f1ae775f864 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests.go @@ -70,7 +70,6 @@ func CreateSchedulerStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.Ma // GetStaticPodSpecs returns all staticPodSpecs actualized to the context of the current MasterConfiguration // NB. this methods holds the information about how kubeadm creates static pod mainfests. func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) map[string]v1.Pod { - // Get the required hostpath mounts mounts := getHostPathVolumesForTheControlPlane(cfg) @@ -110,7 +109,6 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version. // createStaticPodFiles creates all the requested static pod files. func createStaticPodFiles(manifestDir string, cfg *kubeadmapi.MasterConfiguration, componentNames ...string) error { - // TODO: Move the "pkg/util/version".Version object into the internal API instead of always parsing the string k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) if err != nil { @@ -210,12 +208,15 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *versio command = append(command, "--endpoint-reconciler-type="+reconcilers.LeaseEndpointReconcilerType) } + if features.Enabled(cfg.FeatureGates, features.DynamicKubeletConfig) { + command = append(command, "--feature-gates=DynamicKubeletConfig=true") + } + return command } // getControllerManagerCommand builds the right controller manager command from the given config object and version func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) []string { - defaultArguments := map[string]string{ "address": "127.0.0.1", "leader-elect": "true", diff --git a/cmd/kubeadm/app/phases/kubelet/BUILD b/cmd/kubeadm/app/phases/kubelet/BUILD new file mode 100644 index 00000000000..bba6c62ad69 --- /dev/null +++ b/cmd/kubeadm/app/phases/kubelet/BUILD @@ -0,0 +1,56 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["kubelet.go"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet", + visibility = ["//visibility:public"], + deps = [ + "//cmd/kubeadm/app/apis/kubeadm: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", + "//pkg/apis/rbac/v1:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/rbac/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//vendor/k8s.io/client-go/kubernetes:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["kubelet_test.go"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet", + library = ":go_default_library", + deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", + "//cmd/kubeadm/app/constants:go_default_library", + "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) diff --git a/cmd/kubeadm/app/phases/kubelet/kubelet.go b/cmd/kubeadm/app/phases/kubelet/kubelet.go new file mode 100644 index 00000000000..af0d5dc7692 --- /dev/null +++ b/cmd/kubeadm/app/phases/kubelet/kubelet.go @@ -0,0 +1,144 @@ +/* +Copyright 2017 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 kubelet + +import ( + "encoding/json" + "fmt" + + "k8s.io/api/core/v1" + rbac "k8s.io/api/rbac/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/util/wait" + clientset "k8s.io/client-go/kubernetes" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1" + kubeletconfigscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" +) + +// CreateBaseKubeletConfiguration creates base kubelet configuration for dynamic kubelet configuration feature. +func CreateBaseKubeletConfiguration(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error { + _, kubeletCodecs, err := kubeletconfigscheme.NewSchemeAndCodecs() + if err != nil { + return err + } + kubeletBytes, err := kubeadmutil.MarshalToYamlForCodecs(cfg.KubeletConfiguration.BaseConfig, kubeletconfigv1alpha1.SchemeGroupVersion, *kubeletCodecs) + if err != nil { + return err + } + + if err = apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, + Namespace: metav1.NamespaceSystem, + }, + Data: map[string]string{ + kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(kubeletBytes), + }, + }); err != nil { + return err + } + + if err := createKubeletBaseConfigMapRBACRules(client); err != nil { + return fmt.Errorf("error creating base kubelet configmap RBAC rules: %v", err) + } + + return UpdateNodeWithConfigMap(client, cfg.NodeName) +} + +// UpdateNodeWithConfigMap updates node ConfigSource with KubeletBaseConfigurationConfigMap +func UpdateNodeWithConfigMap(client clientset.Interface, nodeName string) error { + // Loop on every falsy return. Return with an error if raised. Exit successfully if true is returned. + return wait.Poll(kubeadmconstants.APICallRetryInterval, kubeadmconstants.UpdateNodeTimeout, func() (bool, error) { + node, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}) + if err != nil { + return false, nil + } + + oldData, err := json.Marshal(node) + if err != nil { + return false, err + } + + kubeletCfg, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(kubeadmconstants.KubeletBaseConfigurationConfigMap, metav1.GetOptions{}) + if err != nil { + return false, nil + } + + node.Spec.ConfigSource.ConfigMapRef.UID = kubeletCfg.UID + + newData, err := json.Marshal(node) + if err != nil { + return false, err + } + + patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{}) + if err != nil { + return false, err + } + + if _, err := client.CoreV1().Nodes().Patch(node.Name, types.StrategicMergePatchType, patchBytes); err != nil { + if apierrs.IsConflict(err) { + fmt.Println("Temporarily unable to update node metadata due to conflict (will retry)") + return false, nil + } + return false, err + } + + return true, nil + }) +} + +// createKubeletBaseConfigMapRBACRules creates the RBAC rules for exposing the base kubelet ConfigMap in the kube-system namespace to unauthenticated users +func createKubeletBaseConfigMapRBACRules(client clientset.Interface) error { + if err := apiclient.CreateOrUpdateRole(client, &rbac.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigMapRoleName, + Namespace: metav1.NamespaceSystem, + }, + Rules: []rbac.PolicyRule{ + rbachelper.NewRule("get").Groups("").Resources("configmaps").Names(kubeadmconstants.KubeletBaseConfigurationConfigMap).RuleOrDie(), + }, + }); err != nil { + return err + } + + return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigMapRoleName, + Namespace: metav1.NamespaceSystem, + }, + RoleRef: rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "Role", + Name: kubeadmconstants.KubeletBaseConfigMapRoleName, + }, + Subjects: []rbac.Subject{ + { + Kind: "Group", + Name: kubeadmconstants.NodesGroup, + }, + }, + }) +} diff --git a/cmd/kubeadm/app/phases/kubelet/kubelet_test.go b/cmd/kubeadm/app/phases/kubelet/kubelet_test.go new file mode 100644 index 00000000000..5c1d5c012c8 --- /dev/null +++ b/cmd/kubeadm/app/phases/kubelet/kubelet_test.go @@ -0,0 +1,134 @@ +/* +Copyright 2017 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 kubelet + +import ( + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/testing" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" +) + +func TestCreateBaseKubeletConfiguration(t *testing.T) { + nodeName := "fake-node" + client := fake.NewSimpleClientset() + cfg := &kubeadmapi.MasterConfiguration{ + NodeName: nodeName, + KubeletConfiguration: kubeadmapi.KubeletConfiguration{ + BaseConfig: &kubeletconfigv1alpha1.KubeletConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeletConfiguration", + }, + }, + }, + } + + client.PrependReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) { + return true, &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + }, + Spec: v1.NodeSpec{ + ConfigSource: &v1.NodeConfigSource{ + ConfigMapRef: &v1.ObjectReference{ + UID: "", + }, + }, + }, + }, nil + }) + client.PrependReactor("get", "configmaps", func(action core.Action) (bool, runtime.Object, error) { + return true, &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, + Namespace: metav1.NamespaceSystem, + UID: "fake-uid", + }, + }, nil + }) + client.PrependReactor("patch", "nodes", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("create", "roles", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("create", "rolebindings", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("create", "configmaps", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + + if err := CreateBaseKubeletConfiguration(cfg, client); err != nil { + t.Errorf("CreateBaseKubeletConfiguration: unexepected error %v", err) + } +} + +func TestUpdateNodeWithConfigMap(t *testing.T) { + nodeName := "fake-node" + client := fake.NewSimpleClientset() + client.PrependReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) { + return true, &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + }, + Spec: v1.NodeSpec{ + ConfigSource: &v1.NodeConfigSource{ + ConfigMapRef: &v1.ObjectReference{ + UID: "", + }, + }, + }, + }, nil + }) + client.PrependReactor("get", "configmaps", func(action core.Action) (bool, runtime.Object, error) { + return true, &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, + Namespace: metav1.NamespaceSystem, + UID: "fake-uid", + }, + }, nil + }) + client.PrependReactor("patch", "nodes", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + + if err := UpdateNodeWithConfigMap(client, nodeName); err != nil { + t.Errorf("UpdateNodeWithConfigMap: unexepected error %v", err) + } +} + +func TestCreateKubeletBaseConfigMapRBACRules(t *testing.T) { + client := fake.NewSimpleClientset() + client.PrependReactor("create", "roles", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("create", "rolebindings", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + + if err := createKubeletBaseConfigMapRBACRules(client); err != nil { + t.Errorf("createKubeletBaseConfigMapRBACRules: unexepected error %v", err) + } +} diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 5f6665961e2..9b096eed599 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -22,6 +22,7 @@ go_library( "//cmd/kubeadm/app/preflight:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", ], diff --git a/cmd/kubeadm/app/util/marshal.go b/cmd/kubeadm/app/util/marshal.go index f44180da3ac..67281d04732 100644 --- a/cmd/kubeadm/app/util/marshal.go +++ b/cmd/kubeadm/app/util/marshal.go @@ -21,17 +21,23 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" clientsetscheme "k8s.io/client-go/kubernetes/scheme" ) // MarshalToYaml marshals an object into yaml. func MarshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) { + return MarshalToYamlForCodecs(obj, gv, clientsetscheme.Codecs) +} + +// MarshalToYamlForCodecs marshals an object into yaml using the specified codec +func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) { mediaType := "application/yaml" - info, ok := runtime.SerializerInfoForMediaType(clientsetscheme.Codecs.SupportedMediaTypes(), mediaType) + info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType) if !ok { return []byte{}, fmt.Errorf("unsupported media type %q", mediaType) } - encoder := clientsetscheme.Codecs.EncoderForVersion(info.Serializer, gv) + encoder := codecs.EncoderForVersion(info.Serializer, gv) return runtime.Encode(encoder, obj) } diff --git a/pkg/util/pointer/pointer.go b/pkg/util/pointer/pointer.go index 1a10939bae2..a970bf7f582 100644 --- a/pkg/util/pointer/pointer.go +++ b/pkg/util/pointer/pointer.go @@ -60,3 +60,9 @@ func Int32PtrDerefOr(ptr *int32, def int32) int32 { } return def } + +// BoolPtr returns a pointer to a bool +func BoolPtr(b bool) *bool { + o := b + return &o +}