mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 22:17:14 +00:00
Merge pull request #84118 from neolit123/1.17-kubeadm-add-kubelet-post-phase
kubeadm: enable kubelet client certificate rotation on primary CP nodes
This commit is contained in:
commit
9648d56765
@ -63,21 +63,21 @@ var (
|
|||||||
{{if .ControlPlaneEndpoint -}}
|
{{if .ControlPlaneEndpoint -}}
|
||||||
{{if .UploadCerts -}}
|
{{if .UploadCerts -}}
|
||||||
You can now join any number of the control-plane node running the following command on each as root:
|
You can now join any number of the control-plane node running the following command on each as root:
|
||||||
|
|
||||||
{{.joinControlPlaneCommand}}
|
{{.joinControlPlaneCommand}}
|
||||||
|
|
||||||
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
|
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
|
||||||
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
|
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
|
||||||
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
|
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
|
||||||
|
|
||||||
{{else -}}
|
{{else -}}
|
||||||
You can now join any number of control-plane nodes by copying certificate authorities
|
You can now join any number of control-plane nodes by copying certificate authorities
|
||||||
and service account keys on each node and then running the following as root:
|
and service account keys on each node and then running the following as root:
|
||||||
|
|
||||||
{{.joinControlPlaneCommand}}
|
{{.joinControlPlaneCommand}}
|
||||||
|
|
||||||
{{end}}{{end}}Then you can join any number of worker nodes by running the following on each as root:
|
{{end}}{{end}}Then you can join any number of worker nodes by running the following on each as root:
|
||||||
|
|
||||||
{{.joinWorkerCommand}}
|
{{.joinWorkerCommand}}
|
||||||
`)))
|
`)))
|
||||||
)
|
)
|
||||||
@ -182,6 +182,7 @@ func NewCmdInit(out io.Writer, initOptions *initOptions) *cobra.Command {
|
|||||||
initRunner.AppendPhase(phases.NewUploadCertsPhase())
|
initRunner.AppendPhase(phases.NewUploadCertsPhase())
|
||||||
initRunner.AppendPhase(phases.NewMarkControlPlanePhase())
|
initRunner.AppendPhase(phases.NewMarkControlPlanePhase())
|
||||||
initRunner.AppendPhase(phases.NewBootstrapTokenPhase())
|
initRunner.AppendPhase(phases.NewBootstrapTokenPhase())
|
||||||
|
initRunner.AppendPhase(phases.NewKubeletFinalizePhase())
|
||||||
initRunner.AppendPhase(phases.NewAddonPhase())
|
initRunner.AppendPhase(phases.NewAddonPhase())
|
||||||
|
|
||||||
// sets the data builder function, that will be used by the runner
|
// sets the data builder function, that will be used by the runner
|
||||||
|
@ -11,6 +11,7 @@ go_library(
|
|||||||
"etcd.go",
|
"etcd.go",
|
||||||
"kubeconfig.go",
|
"kubeconfig.go",
|
||||||
"kubelet.go",
|
"kubelet.go",
|
||||||
|
"kubeletfinalize.go",
|
||||||
"markcontrolplane.go",
|
"markcontrolplane.go",
|
||||||
"preflight.go",
|
"preflight.go",
|
||||||
"uploadcerts.go",
|
"uploadcerts.go",
|
||||||
@ -47,6 +48,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||||
"//vendor/github.com/lithammer/dedent:go_default_library",
|
"//vendor/github.com/lithammer/dedent:go_default_library",
|
||||||
"//vendor/github.com/pkg/errors:go_default_library",
|
"//vendor/github.com/pkg/errors:go_default_library",
|
||||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
package phases
|
package phases
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
||||||
@ -76,7 +78,7 @@ func runKubeletStart(c workflow.RunData) error {
|
|||||||
|
|
||||||
// Try to start the kubelet service in case it's inactive
|
// Try to start the kubelet service in case it's inactive
|
||||||
if !data.DryRun() {
|
if !data.DryRun() {
|
||||||
klog.V(1).Infoln("Starting the kubelet")
|
fmt.Println("[kubelet-start] Starting the kubelet")
|
||||||
kubeletphase.TryStartKubelet()
|
kubeletphase.TryStartKubelet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
136
cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go
Normal file
136
cmd/kubeadm/app/cmd/phases/init/kubeletfinalize.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 phases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
clientcmd "k8s.io/client-go/tools/clientcmd"
|
||||||
|
"k8s.io/klog"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
|
||||||
|
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||||
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
|
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kubeletFinalizePhaseExample = cmdutil.Examples(`
|
||||||
|
# Updates settings relevant to the kubelet after TLS bootstrap"
|
||||||
|
kubeadm init phase kubelet-finalize all --config
|
||||||
|
`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewKubeletFinalizePhase creates a kubeadm workflow phase that updates settings
|
||||||
|
// relevant to the kubelet after TLS bootstrap.
|
||||||
|
func NewKubeletFinalizePhase() workflow.Phase {
|
||||||
|
return workflow.Phase{
|
||||||
|
Name: "kubelet-finalize",
|
||||||
|
Short: "Updates settings relevant to the kubelet after TLS bootstrap",
|
||||||
|
Example: kubeletFinalizePhaseExample,
|
||||||
|
Phases: []workflow.Phase{
|
||||||
|
{
|
||||||
|
Name: "all",
|
||||||
|
Short: "Run all kubelet-finalize phases",
|
||||||
|
InheritFlags: []string{options.CfgPath, options.CertificatesDir},
|
||||||
|
Example: kubeletFinalizePhaseExample,
|
||||||
|
RunAllSiblings: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "experimental-cert-rotation",
|
||||||
|
Short: "Enable kubelet client certificate rotation",
|
||||||
|
InheritFlags: []string{options.CfgPath, options.CertificatesDir},
|
||||||
|
Run: runKubeletFinalizeCertRotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runKubeletFinalizeCertRotation detects if the kubelet certificate rotation is enabled
|
||||||
|
// and updates the kubelet.conf file to point to a rotatable certificate and key for the
|
||||||
|
// Node user.
|
||||||
|
func runKubeletFinalizeCertRotation(c workflow.RunData) error {
|
||||||
|
data, ok := c.(InitData)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("kubelet-finalize phase invoked with an invalid data struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user has added the kubelet --cert-dir flag.
|
||||||
|
// If yes, use that path, else use the kubeadm provided value.
|
||||||
|
cfg := data.Cfg()
|
||||||
|
pkiPath := filepath.Join(data.KubeletDir(), "pki")
|
||||||
|
val, ok := cfg.NodeRegistration.KubeletExtraArgs["cert-dir"]
|
||||||
|
if ok {
|
||||||
|
pkiPath = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the existence of the kubelet-client-current.pem file in the kubelet certificate directory.
|
||||||
|
rotate := false
|
||||||
|
pemPath := filepath.Join(pkiPath, "kubelet-client-current.pem")
|
||||||
|
if _, err := os.Stat(pemPath); err == nil {
|
||||||
|
klog.V(1).Infof("[kubelet-finalize] Assuming that kubelet client certificate rotation is enabled: found %q", pemPath)
|
||||||
|
rotate = true
|
||||||
|
} else {
|
||||||
|
klog.V(1).Infof("[kubelet-finalize] Assuming that kubelet client certificate rotation is disabled: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit early if rotation is disabled.
|
||||||
|
if !rotate {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeconfigPath := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)
|
||||||
|
fmt.Printf("[kubelet-finalize] Updating %q to point to a rotatable kubelet client certificate and key\n", kubeconfigPath)
|
||||||
|
|
||||||
|
// Exit early if dry-running is enabled.
|
||||||
|
if data.DryRun() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the kubeconfig from disk.
|
||||||
|
kubeconfig, err := clientcmd.LoadFromFile(kubeconfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not load %q", kubeconfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform basic validation. The errors here can only happen if the kubelet.conf was corrupted.
|
||||||
|
userName := fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name)
|
||||||
|
info, ok := kubeconfig.AuthInfos[userName]
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("the file %q does not contain authentication for user %q", kubeconfigPath, cfg.NodeRegistration.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the client certificate and key of the node authorizer to point to the PEM symbolic link.
|
||||||
|
info.ClientKeyData = []byte{}
|
||||||
|
info.ClientCertificateData = []byte{}
|
||||||
|
info.ClientKey = pemPath
|
||||||
|
info.ClientCertificate = pemPath
|
||||||
|
|
||||||
|
// Writes the kubeconfig back to disk.
|
||||||
|
if err = clientcmd.WriteToFile(*kubeconfig, kubeconfigPath); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to serialize %q", kubeconfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart the kubelet.
|
||||||
|
klog.V(1).Info("[kubelet-finalize] Restarting the kubelet to enable client certificate rotation")
|
||||||
|
kubeletphase.TryRestartKubelet()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -147,7 +147,7 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to start the kubelet service in case it's inactive
|
// Try to start the kubelet service in case it's inactive
|
||||||
klog.V(1).Infoln("[kubelet-start] Starting the kubelet")
|
fmt.Println("[kubelet-start] Starting the kubelet")
|
||||||
kubeletphase.TryStartKubelet()
|
kubeletphase.TryStartKubelet()
|
||||||
|
|
||||||
// Now the kubelet will perform the TLS Bootstrap, transforming /etc/kubernetes/bootstrap-kubelet.conf to /etc/kubernetes/kubelet.conf
|
// Now the kubelet will perform the TLS Bootstrap, transforming /etc/kubernetes/bootstrap-kubelet.conf to /etc/kubernetes/kubelet.conf
|
||||||
|
@ -35,7 +35,6 @@ func TryStartKubelet() {
|
|||||||
fmt.Println("[kubelet-start] couldn't detect a kubelet service, can't make sure the kubelet is running properly.")
|
fmt.Println("[kubelet-start] couldn't detect a kubelet service, can't make sure the kubelet is running properly.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("[kubelet-start] Activating the kubelet service")
|
|
||||||
// This runs "systemctl daemon-reload && systemctl restart kubelet"
|
// This runs "systemctl daemon-reload && systemctl restart kubelet"
|
||||||
if err := initSystem.ServiceRestart("kubelet"); err != nil {
|
if err := initSystem.ServiceRestart("kubelet"); err != nil {
|
||||||
fmt.Printf("[kubelet-start] WARNING: unable to start the kubelet service: [%v]\n", err)
|
fmt.Printf("[kubelet-start] WARNING: unable to start the kubelet service: [%v]\n", err)
|
||||||
@ -61,3 +60,22 @@ func TryStopKubelet() {
|
|||||||
fmt.Printf("[kubelet-start] WARNING: unable to stop the kubelet service momentarily: [%v]\n", err)
|
fmt.Printf("[kubelet-start] WARNING: unable to stop the kubelet service momentarily: [%v]\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TryRestartKubelet attempts to restart the kubelet service
|
||||||
|
func TryRestartKubelet() {
|
||||||
|
// If we notice that the kubelet service is inactive, try to start it
|
||||||
|
initSystem, err := initsystem.GetInitSystem()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[kubelet-start] no supported init system detected, won't make sure the kubelet not running for a short period of time while setting up configuration for it.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !initSystem.ServiceExists("kubelet") {
|
||||||
|
fmt.Println("[kubelet-start] couldn't detect a kubelet service, can't make sure the kubelet not running for a short period of time while setting up configuration for it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This runs "systemctl daemon-reload && systemctl stop kubelet"
|
||||||
|
if err := initSystem.ServiceRestart("kubelet"); err != nil {
|
||||||
|
fmt.Printf("[kubelet-start] WARNING: unable to restart the kubelet service momentarily: [%v]\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user