kubeadm: Make the self-hosting with certificates in Secrets mode work again

This commit is contained in:
Lucas Käldström 2017-08-19 00:45:16 +03:00
parent ca89308227
commit d1acdf1627
No known key found for this signature in database
GPG Key ID: 3FA3783D77751514
10 changed files with 366 additions and 252 deletions

View File

@ -113,6 +113,12 @@ const (
// SelfHostingPrefix describes the prefix workloads that are self-hosted by kubeadm has
SelfHostingPrefix = "self-hosted-"
// KubeCertificatesVolumeName specifies the name for the Volume that is used for injecting certificates to control plane components (can be both a hostPath volume or a projected, all-in-one volume)
KubeCertificatesVolumeName = "k8s-certs"
// KubeConfigVolumeName specifies the name for the Volume that is used for injecting the kubeconfig to talk securely to the api server for a control plane component if applicable
KubeConfigVolumeName = "kubeconfig"
// NodeBootstrapTokenAuthGroup specifies which group a Node Bootstrap Token should be authenticated in
// TODO: This should be changed in the v1.8 dev cycle to a node-BT-specific group instead of the generic Bootstrap Token group that is used now
NodeBootstrapTokenAuthGroup = "system:bootstrappers"

View File

@ -28,6 +28,7 @@ import (
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/images"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
"k8s.io/kubernetes/pkg/util/version"
@ -159,7 +160,7 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *versio
}
command := []string{"kube-apiserver"}
command = append(command, staticpodutil.GetExtraParameters(cfg.APIServerExtraArgs, defaultArguments)...)
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.APIServerExtraArgs)...)
command = append(command, getAuthzParameters(cfg.AuthorizationModes)...)
// Check if the user decided to use an external etcd cluster
@ -206,7 +207,7 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion
}
command := []string{"kube-controller-manager"}
command = append(command, staticpodutil.GetExtraParameters(cfg.ControllerManagerExtraArgs, defaultArguments)...)
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.ControllerManagerExtraArgs)...)
if cfg.CloudProvider != "" {
command = append(command, "--cloud-provider="+cfg.CloudProvider)
@ -234,7 +235,7 @@ func getSchedulerCommand(cfg *kubeadmapi.MasterConfiguration) []string {
}
command := []string{"kube-scheduler"}
command = append(command, staticpodutil.GetExtraParameters(cfg.SchedulerExtraArgs, defaultArguments)...)
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.SchedulerExtraArgs)...)
return command
}

View File

@ -30,11 +30,9 @@ import (
)
const (
k8sCertsVolumeName = "k8s-certs"
caCertsVolumeName = "ca-certs"
caCertsVolumePath = "/etc/ssl/certs"
caCertsPkiVolumeName = "ca-certs-etc-pki"
kubeConfigVolumeName = "kubeconfig"
)
// caCertsPkiVolumePath specifies the path that can be conditionally mounted into the apiserver and controller-manager containers
@ -49,7 +47,7 @@ func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.MasterConfiguration) c
// HostPath volumes for the API Server
// Read-only mount for the certificates directory
// TODO: Always mount the K8s Certificates directory to a static path inside of the container
mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, k8sCertsVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true)
mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeCertificatesVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true)
// Read-only mount for the ca certs (/etc/ssl/certs) directory
mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, caCertsVolumeName, caCertsVolumePath, caCertsVolumePath, true)
@ -62,17 +60,17 @@ func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.MasterConfiguration) c
// HostPath volumes for the controller manager
// Read-only mount for the certificates directory
// TODO: Always mount the K8s Certificates directory to a static path inside of the container
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, k8sCertsVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true)
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeCertificatesVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true)
// Read-only mount for the ca certs (/etc/ssl/certs) directory
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, caCertsVolumeName, caCertsVolumePath, caCertsVolumePath, true)
// Read-only mount for the controller manager kubeconfig file
controllerManagerKubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName)
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeConfigVolumeName, controllerManagerKubeConfigFile, controllerManagerKubeConfigFile, true)
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeConfigVolumeName, controllerManagerKubeConfigFile, controllerManagerKubeConfigFile, true)
// HostPath volumes for the scheduler
// Read-only mount for the scheduler kubeconfig file
schedulerKubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName)
mounts.NewHostPathMount(kubeadmconstants.KubeScheduler, kubeConfigVolumeName, schedulerKubeConfigFile, schedulerKubeConfigFile, true)
mounts.NewHostPathMount(kubeadmconstants.KubeScheduler, kubeadmconstants.KubeConfigVolumeName, schedulerKubeConfigFile, schedulerKubeConfigFile, true)
// On some systems were we host-mount /etc/ssl/certs, it is also required to mount /etc/pki. This is needed
// due to symlinks pointing from files in /etc/ssl/certs into /etc/pki/

View File

@ -21,6 +21,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/images"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
)
@ -60,6 +61,6 @@ func getEtcdCommand(cfg *kubeadmapi.MasterConfiguration) []string {
}
command := []string{"etcd"}
command = append(command, staticpodutil.GetExtraParameters(cfg.Etcd.ExtraArgs, defaultArguments)...)
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.Etcd.ExtraArgs)...)
return command
}

View File

@ -17,43 +17,55 @@ limitations under the License.
package selfhosting
import (
"path/filepath"
"k8s.io/api/core/v1"
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"
)
// mutatePodSpec makes a Static Pod-hosted PodSpec suitable for self-hosting
func mutatePodSpec(cfg *kubeadmapi.MasterConfiguration, name string, podSpec *v1.PodSpec) {
mutators := map[string][]func(*kubeadmapi.MasterConfiguration, *v1.PodSpec){
const (
// selfHostedKubeConfigDir sets the directory where kubeconfig files for the scheduler and controller-manager should be mounted
// Due to how the projected volume mount works (can only be a full directory, not mount individual files), we must change this from
// the default as mounts cannot be nested (/etc/kubernetes would override /etc/kubernetes/pki)
selfHostedKubeConfigDir = "/etc/kubernetes/kubeconfig"
)
// PodSpecMutatorFunc is a function capable of mutating a PodSpec
type PodSpecMutatorFunc func(*v1.PodSpec)
// getDefaultMutators gets the mutator functions that alwasy should be used
func getDefaultMutators() map[string][]PodSpecMutatorFunc {
return map[string][]PodSpecMutatorFunc{
kubeadmconstants.KubeAPIServer: {
addNodeSelectorToPodSpec,
setMasterTolerationOnPodSpec,
setRightDNSPolicyOnPodSpec,
setVolumesOnKubeAPIServerPodSpec,
},
kubeadmconstants.KubeControllerManager: {
addNodeSelectorToPodSpec,
setMasterTolerationOnPodSpec,
setRightDNSPolicyOnPodSpec,
setVolumesOnKubeControllerManagerPodSpec,
},
kubeadmconstants.KubeScheduler: {
addNodeSelectorToPodSpec,
setMasterTolerationOnPodSpec,
setRightDNSPolicyOnPodSpec,
setVolumesOnKubeSchedulerPodSpec,
},
}
}
// mutatePodSpec makes a Static Pod-hosted PodSpec suitable for self-hosting
func mutatePodSpec(mutators map[string][]PodSpecMutatorFunc, name string, podSpec *v1.PodSpec) {
// Get the mutator functions for the component in question, then loop through and execute them
mutatorsForComponent := mutators[name]
for _, mutateFunc := range mutatorsForComponent {
mutateFunc(cfg, podSpec)
mutateFunc(podSpec)
}
}
// addNodeSelectorToPodSpec makes Pod require to be scheduled on a node marked with the master label
func addNodeSelectorToPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) {
func addNodeSelectorToPodSpec(podSpec *v1.PodSpec) {
if podSpec.NodeSelector == nil {
podSpec.NodeSelector = map[string]string{kubeadmconstants.LabelNodeRoleMaster: ""}
return
@ -63,7 +75,7 @@ func addNodeSelectorToPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.P
}
// setMasterTolerationOnPodSpec makes the Pod tolerate the master taint
func setMasterTolerationOnPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) {
func setMasterTolerationOnPodSpec(podSpec *v1.PodSpec) {
if podSpec.Tolerations == nil {
podSpec.Tolerations = []v1.Toleration{kubeadmconstants.MasterToleration}
return
@ -73,38 +85,68 @@ func setMasterTolerationOnPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *
}
// setRightDNSPolicyOnPodSpec makes sure the self-hosted components can look up things via kube-dns if necessary
func setRightDNSPolicyOnPodSpec(cfg *kubeadmapi.MasterConfiguration, podSpec *v1.PodSpec) {
func setRightDNSPolicyOnPodSpec(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(apiServerVolume, 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(controllerManagerVolume, 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(schedulerVolume, 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) {
// setSelfHostedVolumesForAPIServer makes sure the self-hosted api server has the right volume source coming from a self-hosted cluster
func setSelfHostedVolumesForAPIServer(podSpec *v1.PodSpec) {
for i, v := range podSpec.Volumes {
if v.Name == "k8s" {
podSpec.Volumes[i] = cb(cfg)
// If the volume name matches the expected one; switch the volume source from hostPath to cluster-hosted
if v.Name == kubeadmconstants.KubeCertificatesVolumeName {
podSpec.Volumes[i].VolumeSource = apiServerCertificatesVolumeSource()
}
}
}
// setSelfHostedVolumesForControllerManager makes sure the self-hosted controller manager has the right volume source coming from a self-hosted cluster
func setSelfHostedVolumesForControllerManager(podSpec *v1.PodSpec) {
for i, v := range podSpec.Volumes {
// If the volume name matches the expected one; switch the volume source from hostPath to cluster-hosted
if v.Name == kubeadmconstants.KubeCertificatesVolumeName {
podSpec.Volumes[i].VolumeSource = controllerManagerCertificatesVolumeSource()
} else if v.Name == kubeadmconstants.KubeConfigVolumeName {
podSpec.Volumes[i].VolumeSource = kubeConfigVolumeSource(kubeadmconstants.ControllerManagerKubeConfigFileName)
}
}
// Change directory for the kubeconfig directory to selfHostedKubeConfigDir
for i, vm := range podSpec.Containers[0].VolumeMounts {
if vm.Name == kubeadmconstants.KubeConfigVolumeName {
podSpec.Containers[0].VolumeMounts[i].MountPath = selfHostedKubeConfigDir
}
}
// Rewrite the --kubeconfig path as the volume mount path may not overlap with certs dir, which it does by default (/etc/kubernetes and /etc/kubernetes/pki)
// This is not a problem with hostPath mounts as hostPath supports mounting one file only, instead of always a full directory. Secrets and Projected Volumes
// don't support that.
podSpec.Containers[0].Command = kubeadmutil.ReplaceArgument(podSpec.Containers[0].Command, func(argMap map[string]string) map[string]string {
argMap["kubeconfig"] = filepath.Join(selfHostedKubeConfigDir, kubeadmconstants.ControllerManagerKubeConfigFileName)
return argMap
})
}
// setSelfHostedVolumesForScheduler makes sure the self-hosted scheduler has the right volume source coming from a self-hosted cluster
func setSelfHostedVolumesForScheduler(podSpec *v1.PodSpec) {
for i, v := range podSpec.Volumes {
// If the volume name matches the expected one; switch the volume source from hostPath to cluster-hosted
if v.Name == kubeadmconstants.KubeConfigVolumeName {
podSpec.Volumes[i].VolumeSource = kubeConfigVolumeSource(kubeadmconstants.SchedulerKubeConfigFileName)
}
}
// Change directory for the kubeconfig directory to selfHostedKubeConfigDir
for i, vm := range podSpec.Containers[0].VolumeMounts {
if vm.Name == kubeadmconstants.KubeConfigVolumeName {
podSpec.Containers[0].VolumeMounts[i].MountPath = selfHostedKubeConfigDir
}
}
// Rewrite the --kubeconfig path as the volume mount path may not overlap with certs dir, which it does by default (/etc/kubernetes and /etc/kubernetes/pki)
// This is not a problem with hostPath mounts as hostPath supports mounting one file only, instead of always a full directory. Secrets and Projected Volumes
// don't support that.
podSpec.Containers[0].Command = kubeadmutil.ReplaceArgument(podSpec.Containers[0].Command, func(argMap map[string]string) map[string]string {
argMap["kubeconfig"] = filepath.Join(selfHostedKubeConfigDir, kubeadmconstants.SchedulerKubeConfigFileName)
return argMap
})
}

View File

@ -37,6 +37,9 @@ import (
const (
// selfHostingWaitTimeout describes the maximum amount of time a self-hosting wait process should wait before timing out
selfHostingWaitTimeout = 2 * time.Minute
// selfHostingFailureThreshold describes how many times kubeadm will retry creating the DaemonSets
selfHostingFailureThreshold uint8 = 5
)
// CreateSelfHostedControlPlane is responsible for turning a Static Pod-hosted control plane to a self-hosted one
@ -53,13 +56,23 @@ const (
// 9. Do that for the kube-apiserver, kube-controller-manager and kube-scheduler in a loop
func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error {
// Here the map of different mutators to use for the control plane's podspec is stored
mutators := getDefaultMutators()
// Some extra work to be done if we should store the control plane certificates in Secrets
if features.Enabled(cfg.FeatureFlags, features.StoreCertsInSecrets) {
if err := createTLSSecrets(cfg, client); err != nil {
// Upload the certificates and kubeconfig files from disk to the cluster as Secrets
if err := uploadTLSSecrets(client, cfg.CertificatesDir); err != nil {
return err
}
if err := createOpaqueSecrets(cfg, client); err != nil {
if err := uploadKubeConfigSecrets(client); err != nil {
return err
}
// Add the store-certs-in-secrets-specific mutators here so that the self-hosted component starts using them
mutators[kubeadmconstants.KubeAPIServer] = append(mutators[kubeadmconstants.KubeAPIServer], setSelfHostedVolumesForAPIServer)
mutators[kubeadmconstants.KubeControllerManager] = append(mutators[kubeadmconstants.KubeControllerManager], setSelfHostedVolumesForControllerManager)
mutators[kubeadmconstants.KubeScheduler] = append(mutators[kubeadmconstants.KubeScheduler], setSelfHostedVolumesForScheduler)
}
for _, componentName := range kubeadmconstants.MasterComponents {
@ -79,10 +92,12 @@ func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client cl
}
// Build a DaemonSet object from the loaded PodSpec
ds := buildDaemonSet(cfg, componentName, podSpec)
ds := buildDaemonSet(componentName, podSpec, mutators)
// Create the DaemonSet in the API Server
if err := apiclient.CreateOrUpdateDaemonSet(client, ds); err != nil {
// Create or update the DaemonSet in the API Server, and retry selfHostingFailureThreshold times if it errors out
if err := apiclient.TryRunCommand(func() error {
return apiclient.CreateOrUpdateDaemonSet(client, ds)
}, selfHostingFailureThreshold); err != nil {
return err
}
@ -115,10 +130,10 @@ func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client cl
}
// buildDaemonSet is responsible for mutating the PodSpec and return a DaemonSet which is suitable for the self-hosting purporse
func buildDaemonSet(cfg *kubeadmapi.MasterConfiguration, name string, podSpec *v1.PodSpec) *extensions.DaemonSet {
func buildDaemonSet(name string, podSpec *v1.PodSpec, mutators map[string][]PodSpecMutatorFunc) *extensions.DaemonSet {
// Mutate the PodSpec so it's suitable for self-hosting
mutatePodSpec(cfg, name, podSpec)
mutatePodSpec(mutators, name, podSpec)
// Return a DaemonSet based on that Spec
return &extensions.DaemonSet{

View File

@ -24,14 +24,8 @@ import (
"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"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/features"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
const (
volumeName = "k8s"
volumeMountName = "k8s"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
)
type tlsKeyPair struct {
@ -40,234 +34,164 @@ type tlsKeyPair struct {
key string
}
func k8sSelfHostedVolumeMount() v1.VolumeMount {
return v1.VolumeMount{
Name: volumeMountName,
MountPath: kubeadmconstants.KubernetesDir,
ReadOnly: true,
}
}
func apiServerVolume(cfg *kubeadmapi.MasterConfiguration) v1.Volume {
var volumeSource v1.VolumeSource
if features.Enabled(cfg.FeatureFlags, features.StoreCertsInSecrets) {
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),
},
func apiServerCertificatesVolumeSource() v1.VolumeSource {
return v1.VolumeSource{
Projected: &v1.ProjectedVolumeSource{
Sources: []v1.VolumeProjection{
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.CACertAndKeyBaseName,
},
Items: []v1.KeyToPath{
{
Key: v1.TLSCertKey,
Path: kubeadmconstants.CACertName,
},
},
},
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.APIServerCertAndKeyBaseName,
},
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.APIServerCertAndKeyBaseName,
},
Items: []v1.KeyToPath{
{
Key: v1.TLSCertKey,
Path: kubeadmconstants.APIServerCertName,
},
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),
},
{
Key: v1.TLSPrivateKeyKey,
Path: kubeadmconstants.APIServerKeyName,
},
},
},
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName,
},
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName,
},
Items: []v1.KeyToPath{
{
Key: v1.TLSCertKey,
Path: kubeadmconstants.APIServerKubeletClientCertName,
},
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),
},
{
Key: v1.TLSPrivateKeyKey,
Path: 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.ServiceAccountKeyBaseName,
},
Items: []v1.KeyToPath{
{
Key: v1.TLSCertKey,
Path: kubeadmconstants.ServiceAccountPublicKeyName,
},
},
},
{
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.FrontProxyCACertAndKeyBaseName,
},
Items: []v1.KeyToPath{
{
Key: v1.TLSCertKey,
Path: kubeadmconstants.FrontProxyCACertName,
},
},
},
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.FrontProxyClientCertAndKeyBaseName,
},
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.FrontProxyClientCertAndKeyBaseName,
},
Items: []v1.KeyToPath{
{
Key: v1.TLSCertKey,
Path: kubeadmconstants.FrontProxyClientCertName,
},
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),
},
{
Key: v1.TLSPrivateKeyKey,
Path: kubeadmconstants.FrontProxyClientKeyName,
},
},
},
},
},
}
} else {
volumeSource = v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: kubeadmconstants.KubernetesDir,
},
}
}
return v1.Volume{
Name: volumeName,
VolumeSource: volumeSource,
},
}
}
func schedulerVolume(cfg *kubeadmapi.MasterConfiguration) v1.Volume {
var volumeSource v1.VolumeSource
if features.Enabled(cfg.FeatureFlags, features.StoreCertsInSecrets) {
volumeSource = v1.VolumeSource{
Projected: &v1.ProjectedVolumeSource{
Sources: []v1.VolumeProjection{
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.SchedulerKubeConfigFileName,
func controllerManagerCertificatesVolumeSource() v1.VolumeSource {
return v1.VolumeSource{
Projected: &v1.ProjectedVolumeSource{
Sources: []v1.VolumeProjection{
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.CACertAndKeyBaseName,
},
Items: []v1.KeyToPath{
{
Key: v1.TLSCertKey,
Path: kubeadmconstants.CACertName,
},
{
Key: v1.TLSPrivateKeyKey,
Path: kubeadmconstants.CAKeyName,
},
},
},
},
{
Secret: &v1.SecretProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: kubeadmconstants.ServiceAccountKeyBaseName,
},
Items: []v1.KeyToPath{
{
Key: v1.TLSPrivateKeyKey,
Path: kubeadmconstants.ServiceAccountPrivateKeyName,
},
},
},
},
},
}
} else {
volumeSource = v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: kubeadmconstants.KubernetesDir,
},
}
}
return v1.Volume{
Name: volumeName,
VolumeSource: volumeSource,
},
}
}
func controllerManagerVolume(cfg *kubeadmapi.MasterConfiguration) v1.Volume {
var volumeSource v1.VolumeSource
if features.Enabled(cfg.FeatureFlags, features.StoreCertsInSecrets) {
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),
},
},
},
},
},
},
}
} else {
volumeSource = v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: kubeadmconstants.KubernetesDir,
},
}
}
return v1.Volume{
Name: volumeName,
VolumeSource: volumeSource,
func kubeConfigVolumeSource(kubeconfigSecretName string) v1.VolumeSource {
return v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: kubeconfigSecretName,
},
}
}
func createTLSSecrets(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error {
func uploadTLSSecrets(client clientset.Interface, certDir string) error {
for _, tlsKeyPair := range getTLSKeyPairs() {
secret, err := createTLSSecretFromFiles(
tlsKeyPair.name,
path.Join(cfg.CertificatesDir, tlsKeyPair.cert),
path.Join(cfg.CertificatesDir, tlsKeyPair.key),
path.Join(certDir, tlsKeyPair.cert),
path.Join(certDir, tlsKeyPair.key),
)
if err != nil {
return err
}
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err != nil {
if err := apiclient.CreateOrUpdateSecret(client, secret); err != nil {
return err
}
fmt.Printf("[self-hosted] Created TLS secret %q from %s and %s\n", tlsKeyPair.name, tlsKeyPair.cert, tlsKeyPair.key)
@ -276,24 +200,22 @@ func createTLSSecrets(cfg *kubeadmapi.MasterConfiguration, client clientset.Inte
return nil
}
func createOpaqueSecrets(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error {
func uploadKubeConfigSecrets(client clientset.Interface) error {
files := []string{
kubeadmconstants.SchedulerKubeConfigFileName,
kubeadmconstants.ControllerManagerKubeConfigFileName,
}
for _, file := range files {
secret, err := createOpaqueSecretFromFile(
file,
path.Join(kubeadmconstants.KubernetesDir, file),
)
kubeConfigPath := path.Join(kubeadmconstants.KubernetesDir, file)
secret, err := createOpaqueSecretFromFile(file, kubeConfigPath)
if err != nil {
return err
}
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err != nil {
if err := apiclient.CreateOrUpdateSecret(client, secret); err != nil {
return err
}
fmt.Printf("[self-hosted] Created secret %q\n", file)
fmt.Printf("[self-hosted] Created secret for kubeconfig file %q\n", file)
}
return nil

View File

@ -43,6 +43,20 @@ func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error
return nil
}
// CreateOrUpdateSecret creates a Secret if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateSecret(client clientset.Interface, secret *v1.Secret) error {
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Create(secret); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create secret: %v", err)
}
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Update(secret); err != nil {
return fmt.Errorf("unable to update secret: %v", err)
}
}
return nil
}
// CreateOrUpdateServiceAccount creates a ServiceAccount if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateServiceAccount(client clientset.Interface, sa *v1.ServiceAccount) error {
if _, err := client.CoreV1().ServiceAccounts(sa.ObjectMeta.Namespace).Create(sa); err != nil {

View File

@ -88,3 +88,22 @@ func WaitForStaticPodToDisappear(client clientset.Interface, timeout time.Durati
return false, nil
})
}
// TryRunCommand runs a function a maximum of failureThreshold times, and retries on error. If failureThreshold is hit; the last error is returned
func TryRunCommand(f func() error, failureThreshold uint8) error {
var numFailures uint8
return wait.PollImmediate(5*time.Second, 20*time.Minute, func() (bool, error) {
err := f()
if err != nil {
numFailures++
// If we've reached the maximum amount of failures, error out
if numFailures == failureThreshold {
return false, err
}
// Retry
return false, nil
}
// The last f() call was a success!
return true, nil
})
}

View File

@ -0,0 +1,96 @@
/*
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 util
import (
"fmt"
"strings"
)
// BuildArgumentListFromMap takes two string-string maps, one with the base arguments and one with optional override arguments
func BuildArgumentListFromMap(baseArguments map[string]string, overrideArguments map[string]string) []string {
var command []string
for k, v := range overrideArguments {
// values of "" are allowed as well
command = append(command, fmt.Sprintf("--%s=%s", k, v))
}
for k, v := range baseArguments {
if _, overrideExists := overrideArguments[k]; !overrideExists {
command = append(command, fmt.Sprintf("--%s=%s", k, v))
}
}
return command
}
// ParseArgumentListToMap parses a CLI argument list in the form "--foo=bar" to a string-string map
func ParseArgumentListToMap(arguments []string) map[string]string {
resultingMap := map[string]string{}
for i, arg := range arguments {
key, val, err := parseArgument(arg)
// Ignore if the first argument doesn't satisfy the criteria, it's most often the binary name
// Warn in all other cases, but don't error out. This can happen only if the user has edited the argument list by hand, so they might know what they are doing
if err != nil {
if i != 0 {
fmt.Printf("[kubeadm] WARNING: The component argument %q could not be parsed correctly. The argument must be of the form %q. Skipping...", arg, "--")
}
continue
}
resultingMap[key] = val
}
return resultingMap
}
// ReplaceArgument gets a command list; converts it to a map for easier modification, runs the provided function that
// returns a new modified map, and then converts the map back to a command string slice
func ReplaceArgument(command []string, argMutateFunc func(map[string]string) map[string]string) []string {
argMap := ParseArgumentListToMap(command)
// Save the first command (the executable) if we're sure it's not an argument (i.e. no --)
var newCommand []string
if len(command) > 0 && !strings.HasPrefix(command[0], "--") {
newCommand = append(newCommand, command[0])
}
newArgMap := argMutateFunc(argMap)
newCommand = append(newCommand, BuildArgumentListFromMap(newArgMap, map[string]string{})...)
return newCommand
}
// parseArgument parses the argument "--foo=bar" to "foo" and "bar"
func parseArgument(arg string) (string, string, error) {
if !strings.HasPrefix(arg, "--") {
return "", "", fmt.Errorf("the argument should start with '--'")
}
if !strings.Contains(arg, "=") {
return "", "", fmt.Errorf("the argument should have a '=' between the flag and the value")
}
// Remove the starting --
arg = strings.TrimPrefix(arg, "--")
// Split the string on =. Return only two substrings, since we want only key/value, but the value can include '=' as well
keyvalSlice := strings.SplitN(arg, "=", 2)
// Make sure both a key and value is present
if len(keyvalSlice) != 2 {
return "", "", fmt.Errorf("the argument must have both a key and a value")
}
if len(keyvalSlice[0]) == 0 {
return "", "", fmt.Errorf("the argument must have a key")
}
return keyvalSlice[0], keyvalSlice[1], nil
}