diff --git a/cmd/kubeadm/app/api/types.go b/cmd/kubeadm/app/api/types.go index 5ff3638d81..ffdd22e431 100644 --- a/cmd/kubeadm/app/api/types.go +++ b/cmd/kubeadm/app/api/types.go @@ -40,6 +40,12 @@ type InitFlags struct { API struct { AdvertiseAddrs []net.IP ExternalDNSNames []string + Etcd struct { + ExternalEndpoints []string + ExternalCAFile string + ExternalCertFile string + ExternalKeyFile string + } } Services struct { CIDR net.IPNet diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 72775ee6e9..c8bfda9a45 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -83,6 +83,25 @@ func NewCmdInit(out io.Writer, s *kubeadmapi.KubeadmConfig) *cobra.Command { `(optional) allow to schedule workload to the node`, ) + // TODO (phase1+) @errordeveloper make the flags below not show up in --help but rather on --advanced-help + + cmd.PersistentFlags().StringSliceVar( + &s.InitFlags.API.Etcd.ExternalEndpoints, "external-etcd-endpoints", []string{}, + `(optional) etcd endpoints to use, in case you have an external cluster.`, + ) + cmd.PersistentFlags().StringVar( + &s.InitFlags.API.Etcd.ExternalCAFile, "external-etcd-cafile", "", + `(optional) etcd certificate authority certificate file."`, + ) + cmd.PersistentFlags().StringVar( + &s.InitFlags.API.Etcd.ExternalCertFile, "external-etcd-certfile", "", + `(optional) etcd client certificate file."`, + ) + cmd.PersistentFlags().StringVar( + &s.InitFlags.API.Etcd.ExternalKeyFile, "external-etcd-keyfile", "", + `(optional) etcd client key file."`, + ) + return cmd } diff --git a/cmd/kubeadm/app/master/manifests.go b/cmd/kubeadm/app/master/manifests.go index 8c77af88cb..00bb9521eb 100644 --- a/cmd/kubeadm/app/master/manifests.go +++ b/cmd/kubeadm/app/master/manifests.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "path" + "strings" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api" "k8s.io/kubernetes/cmd/kubeadm/app/images" @@ -57,30 +58,38 @@ const ( // WriteStaticPodManifests builds manifest objects based on user provided configuration and then dumps it to disk // where kubelet will pick and schedule them. func WriteStaticPodManifests(s *kubeadmapi.KubeadmConfig) error { + // Placeholder for kube-apiserver pod spec command + apiServerCommand := getComponentCommand(apiServer, s) + + // Check if the user decided to use an external etcd cluster + if len(s.InitFlags.API.Etcd.ExternalEndpoints) > 0 { + arg := fmt.Sprintf("--etcd-servers=%s", strings.Join(s.InitFlags.API.Etcd.ExternalEndpoints, ",")) + apiServerCommand = append(apiServerCommand, arg) + } else { + apiServerCommand = append(apiServerCommand, "--etcd-servers=http://127.0.0.1:2379") + } + + // Is etcd secured? + if s.InitFlags.API.Etcd.ExternalCAFile != "" { + etcdCAFileArg := fmt.Sprintf("--etcd-cafile=%s", s.InitFlags.API.Etcd.ExternalCAFile) + apiServerCommand = append(apiServerCommand, etcdCAFileArg) + } + if s.InitFlags.API.Etcd.ExternalCertFile != "" && s.InitFlags.API.Etcd.ExternalKeyFile != "" { + etcdClientFileArg := fmt.Sprintf("--etcd-certfile=%s", s.InitFlags.API.Etcd.ExternalCertFile) + etcdKeyFileArg := fmt.Sprintf("--etcd-keyfile=%s", s.InitFlags.API.Etcd.ExternalKeyFile) + apiServerCommand = append(apiServerCommand, etcdClientFileArg, etcdKeyFileArg) + } + + // Prepare static pod specs staticPodSpecs := map[string]api.Pod{ - // TODO this needs a volume - etcd: componentPod(api.Container{ - Command: []string{ - "/usr/local/bin/etcd", - "--listen-client-urls=http://127.0.0.1:2379", - "--advertise-client-urls=http://127.0.0.1:2379", - "--data-dir=/var/etcd/data", - }, - VolumeMounts: []api.VolumeMount{etcdVolumeMount()}, - Image: images.GetCoreImage(images.KubeEtcdImage, s.EnvParams["etcd_image"]), - LivenessProbe: componentProbe(2379, "/health"), - Name: etcd, - Resources: componentResources("200m"), - }, etcdVolume(s)), - // TODO bind-mount certs in kubeAPIServer: componentPod(api.Container{ Name: kubeAPIServer, Image: images.GetCoreImage(images.KubeAPIServerImage, s.EnvParams["hyperkube_image"]), - Command: getComponentCommand(apiServer, s), - VolumeMounts: []api.VolumeMount{k8sVolumeMount()}, + Command: apiServerCommand, + VolumeMounts: []api.VolumeMount{certsVolumeMount(), k8sVolumeMount()}, LivenessProbe: componentProbe(8080, "/healthz"), Resources: componentResources("250m"), - }, k8sVolume(s)), + }, certsVolume(s), k8sVolume(s)), kubeControllerManager: componentPod(api.Container{ Name: kubeControllerManager, Image: images.GetCoreImage(images.KubeControllerManagerImage, s.EnvParams["hyperkube_image"]), @@ -98,6 +107,23 @@ func WriteStaticPodManifests(s *kubeadmapi.KubeadmConfig) error { }), } + // Add etcd static pod spec only if external etcd is not configured + if len(s.InitFlags.API.Etcd.ExternalEndpoints) == 0 { + staticPodSpecs[etcd] = componentPod(api.Container{ + Name: etcd, + Command: []string{ + "etcd", + "--listen-client-urls=http://127.0.0.1:2379", + "--advertise-client-urls=http://127.0.0.1:2379", + "--data-dir=/var/etcd/data", + }, + VolumeMounts: []api.VolumeMount{certsVolumeMount(), etcdVolumeMount(), k8sVolumeMount()}, + Image: images.GetCoreImage(images.KubeEtcdImage, s.EnvParams["etcd_image"]), + LivenessProbe: componentProbe(2379, "/health"), + Resources: componentResources("200m"), + }, certsVolume(s), etcdVolume(s), k8sVolume(s)) + } + manifestsPath := path.Join(s.EnvParams["kubernetes_dir"], "manifests") if err := os.MkdirAll(manifestsPath, 0700); err != nil { return fmt.Errorf(" failed to create directory %q [%s]", manifestsPath, err) @@ -115,8 +141,7 @@ func WriteStaticPodManifests(s *kubeadmapi.KubeadmConfig) error { return nil } -// etcdVolume returns an host-path volume for storing etcd data. -// By using a host-path, the data will survive pod restart. +// etcdVolume exposes a path on the host in order to guarantee data survival during reboot. func etcdVolume(s *kubeadmapi.KubeadmConfig) api.Volume { return api.Volume{ Name: "etcd", @@ -133,6 +158,24 @@ func etcdVolumeMount() api.VolumeMount { } } +// certsVolume exposes host SSL certificates to pod containers. +func certsVolume(s *kubeadmapi.KubeadmConfig) api.Volume { + return api.Volume{ + Name: "certs", + VolumeSource: api.VolumeSource{ + // TODO make path configurable + HostPath: &api.HostPathVolumeSource{Path: "/etc/ssl/certs"}, + }, + } +} + +func certsVolumeMount() api.VolumeMount { + return api.VolumeMount{ + Name: "certs", + MountPath: "/etc/ssl/certs", + } +} + func k8sVolume(s *kubeadmapi.KubeadmConfig) api.Volume { return api.Volume{ Name: "pki",