diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 8f1e637dd3..555e58a0da 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -271,7 +271,7 @@ func (i *Init) Run(out io.Writer) error { // Temporary control plane is up, now we create our self hosted control // plane components and remove the static manifests: fmt.Println("[self-hosted] Creating self-hosted control plane...") - if err := selfhostingphase.CreateSelfHostedControlPlane(client); err != nil { + if err := selfhostingphase.CreateSelfHostedControlPlane(i.cfg, client); err != nil { return err } } diff --git a/cmd/kubeadm/app/cmd/phases/selfhosting.go b/cmd/kubeadm/app/cmd/phases/selfhosting.go index 9f51ae3c6f..2cd4ca1475 100644 --- a/cmd/kubeadm/app/cmd/phases/selfhosting.go +++ b/cmd/kubeadm/app/cmd/phases/selfhosting.go @@ -19,23 +19,30 @@ package phases import ( "github.com/spf13/cobra" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + "k8s.io/kubernetes/pkg/api" ) // NewCmdSelfhosting returns the self-hosting Cobra command func NewCmdSelfhosting() *cobra.Command { var kubeConfigFile string + cfg := &kubeadmapiext.MasterConfiguration{} cmd := &cobra.Command{ Use: "selfhosting", Aliases: []string{"selfhosted"}, Short: "Make a kubeadm cluster self-hosted.", Run: func(cmd *cobra.Command, args []string) { + api.Scheme.Default(cfg) + internalcfg := &kubeadmapi.MasterConfiguration{} + api.Scheme.Convert(cfg, internalcfg, nil) client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) kubeadmutil.CheckErr(err) - err = selfhosting.CreateSelfHostedControlPlane(client) + err = selfhosting.CreateSelfHostedControlPlane(internalcfg, client) kubeadmutil.CheckErr(err) }, } diff --git a/cmd/kubeadm/app/phases/selfhosting/BUILD b/cmd/kubeadm/app/phases/selfhosting/BUILD index 913e88bfcb..5cd98a4045 100644 --- a/cmd/kubeadm/app/phases/selfhosting/BUILD +++ b/cmd/kubeadm/app/phases/selfhosting/BUILD @@ -13,10 +13,12 @@ go_test( srcs = [ "podspec_mutation_test.go", "selfhosting_test.go", + "selfhosting_volumes_test.go", ], library = ":go_default_library", tags = ["automanaged"], deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", @@ -28,9 +30,11 @@ go_library( srcs = [ "podspec_mutation.go", "selfhosting.go", + "selfhosting_volumes.go", ], tags = ["automanaged"], deps = [ + "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//pkg/api:go_default_library", diff --git a/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go b/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go index 5e5d0f9811..cf7b2edae7 100644 --- a/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go +++ b/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go @@ -18,38 +18,42 @@ package selfhosting import ( "k8s.io/api/core/v1" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) // mutatePodSpec makes a Static Pod-hosted PodSpec suitable for self-hosting -func mutatePodSpec(name string, podSpec *v1.PodSpec) { - mutators := map[string][]func(*v1.PodSpec){ +func mutatePodSpec(cfg *kubeadmapi.MasterConfiguration, name string, podSpec *v1.PodSpec) { + mutators := map[string][]func(*kubeadmapi.MasterConfiguration, *v1.PodSpec){ kubeAPIServer: { addNodeSelectorToPodSpec, setMasterTolerationOnPodSpec, setRightDNSPolicyOnPodSpec, + setVolumesOnKubeAPIServerPodSpec, }, kubeControllerManager: { addNodeSelectorToPodSpec, setMasterTolerationOnPodSpec, setRightDNSPolicyOnPodSpec, + setVolumesOnKubeControllerManagerPodSpec, }, kubeScheduler: { addNodeSelectorToPodSpec, setMasterTolerationOnPodSpec, setRightDNSPolicyOnPodSpec, + setVolumesOnKubeSchedulerPodSpec, }, } // Get the mutator functions for the component in question, then loop through and execute them mutatorsForComponent := mutators[name] for _, mutateFunc := range mutatorsForComponent { - mutateFunc(podSpec) + mutateFunc(cfg, podSpec) } } // addNodeSelectorToPodSpec makes Pod require to be scheduled on a node marked with the master label -func addNodeSelectorToPodSpec(podSpec *v1.PodSpec) { +func addNodeSelectorToPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) { if podSpec.NodeSelector == nil { podSpec.NodeSelector = map[string]string{kubeadmconstants.LabelNodeRoleMaster: ""} return @@ -59,7 +63,7 @@ func addNodeSelectorToPodSpec(podSpec *v1.PodSpec) { } // setMasterTolerationOnPodSpec makes the Pod tolerate the master taint -func setMasterTolerationOnPodSpec(podSpec *v1.PodSpec) { +func setMasterTolerationOnPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) { if podSpec.Tolerations == nil { podSpec.Tolerations = []v1.Toleration{kubeadmconstants.MasterToleration} return @@ -69,6 +73,38 @@ func setMasterTolerationOnPodSpec(podSpec *v1.PodSpec) { } // setRightDNSPolicyOnPodSpec makes sure the self-hosted components can look up things via kube-dns if necessary -func setRightDNSPolicyOnPodSpec(podSpec *v1.PodSpec) { +func setRightDNSPolicyOnPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) { podSpec.DNSPolicy = v1.DNSClusterFirstWithHostNet } + +// setVolumesOnKubeAPIServerPodSpec makes sure the self-hosted api server has the required files +func setVolumesOnKubeAPIServerPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) { + setK8sVolume(apiServerProjectedVolume, cfg, podSpec) + for _, c := range podSpec.Containers { + c.VolumeMounts = append(c.VolumeMounts, k8sSelfHostedVolumeMount()) + } +} + +// setVolumesOnKubeControllerManagerPodSpec makes sure the self-hosted controller manager has the required files +func setVolumesOnKubeControllerManagerPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) { + setK8sVolume(controllerManagerProjectedVolume, cfg, podSpec) + for _, c := range podSpec.Containers { + c.VolumeMounts = append(c.VolumeMounts, k8sSelfHostedVolumeMount()) + } +} + +// setVolumesOnKubeSchedulerPodSpec makes sure the self-hosted scheduler has the required files +func setVolumesOnKubeSchedulerPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) { + setK8sVolume(schedulerProjectedVolume, cfg, podSpec) + for _, c := range podSpec.Containers { + c.VolumeMounts = append(c.VolumeMounts, k8sSelfHostedVolumeMount()) + } +} + +func setK8sVolume(cb func(cfg *kubeadmapi.MasterConfiguration) v1.Volume, cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) { + for i, v := range podSpec.Volumes { + if v.Name == "k8s" { + podSpec.Volumes[i] = cb(cfg) + } + } +} diff --git a/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go b/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go index beb90d8aa0..e84214bf8b 100644 --- a/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go +++ b/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go @@ -21,6 +21,7 @@ import ( "testing" "k8s.io/api/core/v1" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) @@ -71,8 +72,9 @@ func TestMutatePodSpec(t *testing.T) { }, } + cfg := &kubeadmapi.MasterConfiguration{} for _, rt := range tests { - mutatePodSpec(rt.component, rt.podSpec) + mutatePodSpec(cfg, rt.component, rt.podSpec) if !reflect.DeepEqual(*rt.podSpec, rt.expected) { t.Errorf("failed mutatePodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec) @@ -108,8 +110,9 @@ func TestAddNodeSelectorToPodSpec(t *testing.T) { }, } + cfg := &kubeadmapi.MasterConfiguration{} for _, rt := range tests { - addNodeSelectorToPodSpec(rt.podSpec) + addNodeSelectorToPodSpec(cfg, rt.podSpec) if !reflect.DeepEqual(*rt.podSpec, rt.expected) { t.Errorf("failed addNodeSelectorToPodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec) @@ -145,8 +148,9 @@ func TestSetMasterTolerationOnPodSpec(t *testing.T) { }, } + cfg := &kubeadmapi.MasterConfiguration{} for _, rt := range tests { - setMasterTolerationOnPodSpec(rt.podSpec) + setMasterTolerationOnPodSpec(cfg, rt.podSpec) if !reflect.DeepEqual(*rt.podSpec, rt.expected) { t.Errorf("failed setMasterTolerationOnPodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec) @@ -175,8 +179,9 @@ func TestSetRightDNSPolicyOnPodSpec(t *testing.T) { }, } + cfg := &kubeadmapi.MasterConfiguration{} for _, rt := range tests { - setRightDNSPolicyOnPodSpec(rt.podSpec) + setRightDNSPolicyOnPodSpec(cfg, rt.podSpec) if !reflect.DeepEqual(*rt.podSpec, rt.expected) { t.Errorf("failed setRightDNSPolicyOnPodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec) diff --git a/cmd/kubeadm/app/phases/selfhosting/selfhosting.go b/cmd/kubeadm/app/phases/selfhosting/selfhosting.go index 36f003bd2f..421ede33cf 100644 --- a/cmd/kubeadm/app/phases/selfhosting/selfhosting.go +++ b/cmd/kubeadm/app/phases/selfhosting/selfhosting.go @@ -29,6 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kuberuntime "k8s.io/apimachinery/pkg/runtime" 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/pkg/api" @@ -53,7 +54,15 @@ const ( // 7. The self-hosted containers should now step up and take over. // 8. In order to avoid race conditions, we're still making sure the API /healthz endpoint is healthy // 9. Do that for the kube-apiserver, kube-controller-manager and kube-scheduler in a loop -func CreateSelfHostedControlPlane(client *clientset.Clientset) error { +func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client *clientset.Clientset) error { + + if err := createTLSSecrets(cfg, client); err != nil { + return err + } + + if err := createOpaqueSecrets(cfg, client); err != nil { + return err + } // The sequence here isn't set in stone, but seems to work well to start with the API server components := []string{kubeAPIServer, kubeControllerManager, kubeScheduler} @@ -69,7 +78,7 @@ func CreateSelfHostedControlPlane(client *clientset.Clientset) error { } // Build a DaemonSet object from the loaded PodSpec - ds := buildDaemonSet(componentName, podSpec) + ds := buildDaemonSet(cfg, componentName, podSpec) // Create the DaemonSet in the API Server if _, err := client.ExtensionsV1beta1().DaemonSets(metav1.NamespaceSystem).Create(ds); err != nil { @@ -100,9 +109,9 @@ func CreateSelfHostedControlPlane(client *clientset.Clientset) error { } // buildDaemonSet is responsible for mutating the PodSpec and return a DaemonSet which is suitable for the self-hosting purporse -func buildDaemonSet(name string, podSpec *v1.PodSpec) *extensions.DaemonSet { +func buildDaemonSet(cfg *kubeadmapi.MasterConfiguration, name string, podSpec *v1.PodSpec) *extensions.DaemonSet { // Mutate the PodSpec so it's suitable for self-hosting - mutatePodSpec(name, podSpec) + mutatePodSpec(cfg, name, podSpec) // Return a DaemonSet based on that Spec return &extensions.DaemonSet{ diff --git a/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go b/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go index 11b2ce791f..558276b5c2 100644 --- a/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go +++ b/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/ghodss/yaml" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" ) const ( @@ -90,9 +91,49 @@ spec: name: pki hostNetwork: true volumes: - - hostPath: - path: /etc/kubernetes - name: k8s + - name: k8s + projected: + sources: + - secret: + items: + - key: tls.crt + path: ca.crt + - key: tls.key + path: ca.key + name: ca + - secret: + items: + - key: tls.crt + path: apiserver.crt + - key: tls.key + path: apiserver.key + name: apiserver + - secret: + items: + - key: tls.crt + path: apiserver-kubelet-client.crt + - key: tls.key + path: apiserver-kubelet-client.key + name: apiserver-kubelet-client + - secret: + items: + - key: tls.crt + path: sa.pub + - key: tls.key + path: sa.key + name: sa + - secret: + items: + - key: tls.crt + path: front-proxy-ca.crt + name: front-proxy-ca + - secret: + items: + - key: tls.crt + path: front-proxy-client.crt + - key: tls.key + path: front-proxy-client.key + name: front-proxy-client - hostPath: path: /etc/ssl/certs name: certs @@ -171,9 +212,49 @@ spec: - effect: NoSchedule key: node-role.kubernetes.io/master volumes: - - hostPath: - path: /etc/kubernetes - name: k8s + - name: k8s + projected: + sources: + - secret: + items: + - key: tls.crt + path: ca.crt + - key: tls.key + path: ca.key + name: ca + - secret: + items: + - key: tls.crt + path: apiserver.crt + - key: tls.key + path: apiserver.key + name: apiserver + - secret: + items: + - key: tls.crt + path: apiserver-kubelet-client.crt + - key: tls.key + path: apiserver-kubelet-client.key + name: apiserver-kubelet-client + - secret: + items: + - key: tls.crt + path: sa.pub + - key: tls.key + path: sa.key + name: sa + - secret: + items: + - key: tls.crt + path: front-proxy-ca.crt + name: front-proxy-ca + - secret: + items: + - key: tls.crt + path: front-proxy-client.crt + - key: tls.key + path: front-proxy-client.key + name: front-proxy-client - hostPath: path: /etc/ssl/certs name: certs @@ -237,9 +318,23 @@ spec: name: pki hostNetwork: true volumes: - - hostPath: - path: /etc/kubernetes - name: k8s + - name: k8s + projected: + sources: + - secret: + name: controller-manager.conf + - secret: + items: + - key: tls.crt + path: ca.crt + - key: tls.key + path: ca.key + name: ca + - secret: + items: + - key: tls.key + path: sa.key + name: sa - hostPath: path: /etc/ssl/certs name: certs @@ -304,9 +399,23 @@ spec: - effect: NoSchedule key: node-role.kubernetes.io/master volumes: - - hostPath: - path: /etc/kubernetes - name: k8s + - name: k8s + projected: + sources: + - secret: + name: controller-manager.conf + - secret: + items: + - key: tls.crt + path: ca.crt + - key: tls.key + path: ca.key + name: ca + - secret: + items: + - key: tls.key + path: sa.key + name: sa - hostPath: path: /etc/ssl/certs name: certs @@ -360,9 +469,11 @@ spec: readOnly: true hostNetwork: true volumes: - - hostPath: - path: /etc/kubernetes - name: k8s + - name: k8s + projected: + sources: + - secret: + name: scheduler.conf status: {} ` @@ -411,9 +522,11 @@ spec: - effect: NoSchedule key: node-role.kubernetes.io/master volumes: - - hostPath: - path: /etc/kubernetes - name: k8s + - name: k8s + projected: + sources: + - secret: + name: scheduler.conf updateStrategy: {} status: currentNumberScheduled: 0 @@ -455,7 +568,8 @@ func TestBuildDaemonSet(t *testing.T) { t.Fatalf("couldn't load the specified Pod") } - ds := buildDaemonSet(rt.component, podSpec) + cfg := &kubeadmapi.MasterConfiguration{} + ds := buildDaemonSet(cfg, rt.component, podSpec) dsBytes, err := yaml.Marshal(ds) if err != nil { t.Fatalf("failed to marshal daemonset to YAML: %v", err) diff --git a/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes.go b/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes.go new file mode 100644 index 0000000000..1e9a933609 --- /dev/null +++ b/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes.go @@ -0,0 +1,340 @@ +/* +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 selfhosting + +import ( + "fmt" + "io/ioutil" + "path" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientset "k8s.io/client-go/kubernetes" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" +) + +type tlsKeyPair struct { + name string + cert string + key string +} + +func k8sSelfHostedVolumeMount() v1.VolumeMount { + return v1.VolumeMount{ + Name: "k8s", + MountPath: kubeadmapi.GlobalEnvParams.KubernetesDir, + ReadOnly: true, + } +} + +func apiServerProjectedVolume(cfg *kubeadmapi.MasterConfiguration) v1.Volume { + return v1.Volume{ + Name: "k8s", + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{ + Sources: []v1.VolumeProjection{ + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.CACertAndKeyBaseName, + }, + Items: []v1.KeyToPath{ + { + Key: v1.TLSCertKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.CACertName), + }, + { + Key: v1.TLSPrivateKeyKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.CAKeyName), + }, + }, + }, + }, + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.APIServerCertAndKeyBaseName, + }, + Items: []v1.KeyToPath{ + { + Key: v1.TLSCertKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.APIServerCertName), + }, + { + Key: v1.TLSPrivateKeyKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.APIServerKeyName), + }, + }, + }, + }, + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, + }, + Items: []v1.KeyToPath{ + { + Key: v1.TLSCertKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.APIServerKubeletClientCertName), + }, + { + Key: v1.TLSPrivateKeyKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.APIServerKubeletClientKeyName), + }, + }, + }, + }, + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.ServiceAccountKeyBaseName, + }, + Items: []v1.KeyToPath{ + { + Key: v1.TLSCertKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.ServiceAccountPublicKeyName), + }, + { + Key: v1.TLSPrivateKeyKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.ServiceAccountPrivateKeyName), + }, + }, + }, + }, + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.FrontProxyCACertAndKeyBaseName, + }, + Items: []v1.KeyToPath{ + { + Key: v1.TLSCertKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.FrontProxyCACertName), + }, + }, + }, + }, + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.FrontProxyClientCertAndKeyBaseName, + }, + Items: []v1.KeyToPath{ + { + Key: v1.TLSCertKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.FrontProxyClientCertName), + }, + { + Key: v1.TLSPrivateKeyKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.FrontProxyClientKeyName), + }, + }, + }, + }, + }, + }, + }, + } +} + +func schedulerProjectedVolume(cfg *kubeadmapi.MasterConfiguration) v1.Volume { + return v1.Volume{ + Name: "k8s", + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{ + Sources: []v1.VolumeProjection{ + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.SchedulerKubeConfigFileName, + }, + }, + }, + }, + }, + }, + } +} + +func controllerManagerProjectedVolume(cfg *kubeadmapi.MasterConfiguration) v1.Volume { + return v1.Volume{ + Name: "k8s", + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{ + Sources: []v1.VolumeProjection{ + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.ControllerManagerKubeConfigFileName, + }, + }, + }, + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.CACertAndKeyBaseName, + }, + Items: []v1.KeyToPath{ + { + Key: v1.TLSCertKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.CACertName), + }, + { + Key: v1.TLSPrivateKeyKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.CAKeyName), + }, + }, + }, + }, + { + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: kubeadmconstants.ServiceAccountKeyBaseName, + }, + Items: []v1.KeyToPath{ + { + Key: v1.TLSPrivateKeyKey, + Path: path.Join(path.Base(cfg.CertificatesDir), kubeadmconstants.ServiceAccountPrivateKeyName), + }, + }, + }, + }, + }, + }, + }, + } +} + +func createTLSSecrets(cfg *kubeadmapi.MasterConfiguration, client *clientset.Clientset) error { + for _, tlsKeyPair := range getTLSKeyPairs() { + secret, err := createTLSSecretFromFiles( + tlsKeyPair.name, + path.Join(cfg.CertificatesDir, tlsKeyPair.cert), + path.Join(cfg.CertificatesDir, tlsKeyPair.key), + ) + if err != nil { + return err + } + + if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err != nil { + return err + } + fmt.Printf("[self-hosted] Created TLS secret %q from %s and %s\n", tlsKeyPair.name, tlsKeyPair.cert, tlsKeyPair.key) + } + + return nil +} + +func createOpaqueSecrets(cfg *kubeadmapi.MasterConfiguration, client *clientset.Clientset) error { + files := []string{ + kubeadmconstants.SchedulerKubeConfigFileName, + kubeadmconstants.ControllerManagerKubeConfigFileName, + } + for _, file := range files { + secret, err := createOpaqueSecretFromFile( + file, + path.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, file), + ) + if err != nil { + return err + } + + if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err != nil { + return err + } + fmt.Printf("[self-hosted] Created secret %q\n", file) + } + + return nil +} + +func createTLSSecretFromFiles(secretName, crt, key string) (*v1.Secret, error) { + crtBytes, err := ioutil.ReadFile(crt) + if err != nil { + return nil, err + } + keyBytes, err := ioutil.ReadFile(key) + if err != nil { + return nil, err + } + + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: metav1.NamespaceSystem, + }, + Type: v1.SecretTypeTLS, + Data: map[string][]byte{ + v1.TLSCertKey: crtBytes, + v1.TLSPrivateKeyKey: keyBytes, + }, + }, nil +} + +func createOpaqueSecretFromFile(secretName, file string) (*v1.Secret, error) { + fileBytes, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: metav1.NamespaceSystem, + }, + Type: v1.SecretTypeOpaque, + Data: map[string][]byte{ + path.Base(file): fileBytes, + }, + }, nil +} + +func getTLSKeyPairs() []*tlsKeyPair { + return []*tlsKeyPair{ + { + name: kubeadmconstants.CACertAndKeyBaseName, + cert: kubeadmconstants.CACertName, + key: kubeadmconstants.CAKeyName, + }, + { + name: kubeadmconstants.APIServerCertAndKeyBaseName, + cert: kubeadmconstants.APIServerCertName, + key: kubeadmconstants.APIServerKeyName, + }, + { + name: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, + cert: kubeadmconstants.APIServerKubeletClientCertName, + key: kubeadmconstants.APIServerKubeletClientKeyName, + }, + { + name: kubeadmconstants.ServiceAccountKeyBaseName, + cert: kubeadmconstants.ServiceAccountPublicKeyName, + key: kubeadmconstants.ServiceAccountPrivateKeyName, + }, + { + name: kubeadmconstants.FrontProxyCACertAndKeyBaseName, + cert: kubeadmconstants.FrontProxyCACertName, + key: kubeadmconstants.FrontProxyCAKeyName, + }, + { + name: kubeadmconstants.FrontProxyClientCertAndKeyBaseName, + cert: kubeadmconstants.FrontProxyClientCertName, + key: kubeadmconstants.FrontProxyClientKeyName, + }, + } +} diff --git a/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes_test.go b/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes_test.go new file mode 100644 index 0000000000..bf2e51c1f1 --- /dev/null +++ b/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes_test.go @@ -0,0 +1,72 @@ +/* +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 selfhosting + +import ( + "io/ioutil" + "log" + "os" + "testing" +) + +func createTemporaryFile(name string) *os.File { + content := []byte("foo") + tmpfile, err := ioutil.TempFile("", name) + if err != nil { + log.Fatal(err) + } + + if _, err := tmpfile.Write(content); err != nil { + log.Fatal(err) + } + + return tmpfile +} + +func TestCreateTLSSecretFromFile(t *testing.T) { + tmpCert := createTemporaryFile("foo.crt") + defer os.Remove(tmpCert.Name()) + tmpKey := createTemporaryFile("foo.key") + defer os.Remove(tmpKey.Name()) + + _, err := createTLSSecretFromFiles("foo", tmpCert.Name(), tmpKey.Name()) + if err != nil { + log.Fatal(err) + } + + if err := tmpCert.Close(); err != nil { + log.Fatal(err) + } + + if err := tmpKey.Close(); err != nil { + log.Fatal(err) + } +} + +func TestCreateOpaqueSecretFromFile(t *testing.T) { + tmpFile := createTemporaryFile("foo") + defer os.Remove(tmpFile.Name()) + + _, err := createOpaqueSecretFromFile("foo", tmpFile.Name()) + if err != nil { + log.Fatal(err) + } + + if err := tmpFile.Close(); err != nil { + log.Fatal(err) + } +}