mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
kubeadm: Make the self-hosting with certificates in Secrets mode work again
This commit is contained in:
parent
ca89308227
commit
d1acdf1627
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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/
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
96
cmd/kubeadm/app/util/arguments.go
Normal file
96
cmd/kubeadm/app/util/arguments.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user