命令入口
kubeadm init帮助信息
[root@node1 ~]# kubeadm init -h
Run this command in order to set up the Kubernetes control plane
The "init" command executes the following phases:
preflight Run pre-flight checks
certs Certificate generation
/ca Generate the self-signed Kubernetes CA to provision identities for other Kubernetes components
/apiserver Generate the certificate for serving the Kubernetes API
/apiserver-kubelet-client Generate the certificate for the API server to connect to kubelet
/front-proxy-ca Generate the self-signed CA to provision identities for front proxy
/front-proxy-client Generate the certificate for the front proxy client
/etcd-ca Generate the self-signed CA to provision identities for etcd
/etcd-server Generate the certificate for serving etcd
/etcd-peer Generate the certificate for etcd nodes to communicate with each other
/etcd-healthcheck-client Generate the certificate for liveness probes to healthcheck etcd
/apiserver-etcd-client Generate the certificate the apiserver uses to access etcd
/sa Generate a private key for signing service account tokens along with its public key
kubeconfig Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file
/admin Generate a kubeconfig file for the admin to use and for kubeadm itself
/kubelet Generate a kubeconfig file for the kubelet to use *only* for cluster bootstrapping purposes
/controller-manager Generate a kubeconfig file for the controller manager to use
/scheduler Generate a kubeconfig file for the scheduler to use
etcd Generate static Pod manifest file for local etcd
/local Generate the static Pod manifest file for a local, single-node local etcd instance
control-plane Generate all static Pod manifest files necessary to establish the control plane
/apiserver Generates the kube-apiserver static Pod manifest
/controller-manager Generates the kube-controller-manager static Pod manifest
/scheduler Generates the kube-scheduler static Pod manifest
kubelet-start Write kubelet settings and (re)start the kubelet
upload-config Upload the kubeadm and kubelet configuration to a ConfigMap
/kubeadm Upload the kubeadm ClusterConfiguration to a ConfigMap
/kubelet Upload the kubelet component config to a ConfigMap
upload-certs Upload certificates to kubeadm-certs
mark-control-plane Mark a node as a control-plane
bootstrap-token Generates bootstrap tokens used to join a node to a cluster
kubelet-finalize Updates settings relevant to the kubelet after TLS bootstrap
/experimental-cert-rotation Enable kubelet client certificate rotation
addon Install required addons for passing conformance tests
/coredns Install the CoreDNS addon to a Kubernetes cluster
/kube-proxy Install the kube-proxy addon to a Kubernetes cluster
show-join-command Show the join command for control-plane and worker node
Usage:
kubeadm init [flags]
kubeadm init [command]
Available Commands:
phase Use this command to invoke single phase of the init workflow
Flags:
--apiserver-advertise-address string The IP address the API Server will advertise it's listening on. If not set the default network interface will be used.
--apiserver-bind-port int32 Port for the API Server to bind to. (default 6443)
--apiserver-cert-extra-sans strings Optional extra Subject Alternative Names (SANs) to use for the API Server serving certificate. Can be both IP addresses and DNS names.
--cert-dir string The path where to save and store the certificates. (default "/etc/kubernetes/pki")
--certificate-key string Key used to encrypt the control-plane certificates in the kubeadm-certs Secret.
--config string Path to a kubeadm configuration file.
--control-plane-endpoint string Specify a stable IP address or DNS name for the control plane.
--cri-socket string Path to the CRI socket to connect. If empty kubeadm will try to auto-detect this value; use this option only if you have more than one CRI installed or if you have non-standard CRI socket.
--dry-run Don't apply any changes; just output what would be done.
--feature-gates string A set of key=value pairs that describe feature gates for various features. Options are:
EtcdLearnerMode=true|false (ALPHA - default=false)
PublicKeysECDSA=true|false (ALPHA - default=false)
RootlessControlPlane=true|false (ALPHA - default=false)
UpgradeAddonsBeforeControlPlane=true|false (DEPRECATED - default=false)
-h, --help help for init
--ignore-preflight-errors strings A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.
--image-repository string Choose a container registry to pull control plane images from (default "registry.k8s.io")
--kubernetes-version string Choose a specific Kubernetes version for the control plane. (default "stable-1")
--node-name string Specify the node name.
--patches string Path to a directory that contains files named "target[suffix][+patchtype].extension". For example, "kube-apiserver0+merge.yaml" or just "etcd.json". "target" can be one of "kube-apiserver", "kube-controller-manager", "kube-scheduler", "etcd", "kubeletconfiguration". "patchtype" can be one of "strategic", "merge" or "json" and they match the patch formats supported by kubectl. The default "patchtype" is "strategic". "extension" must be either "json" or "yaml". "suffix" is an optional string that can be used to determine which patches are applied first alpha-numerically.
--pod-network-cidr string Specify range of IP addresses for the pod network. If set, the control plane will automatically allocate CIDRs for every node.
--service-cidr string Use alternative range of IP address for service VIPs. (default "10.96.0.0/12")
--service-dns-domain string Use alternative domain for services, e.g. "myorg.internal". (default "cluster.local")
--skip-certificate-key-print Don't print the key used to encrypt the control-plane certificates.
--skip-phases strings List of phases to be skipped
--skip-token-print Skip printing of the default bootstrap token generated by 'kubeadm init'.
--token string The token to use for establishing bidirectional trust between nodes and control-plane nodes. The format is [a-z0-9]{6}\.[a-z0-9]{16} - e.g. abcdef.0123456789abcdef
--token-ttl duration The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire (default 24h0m0s)
--upload-certs Upload control-plane certificates to the kubeadm-certs Secret.
Global Flags:
--add-dir-header If true, adds the file directory to the header of the log messages
--log-file string If non-empty, use this log file (no effect when -logtostderr=true)
--log-file-max-size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
--one-output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true)
--rootfs string [EXPERIMENTAL] The path to the 'real' host root filesystem.
--skip-headers If true, avoid header prefixes in the log messages
--skip-log-headers If true, avoid headers when opening log files (no effect when -logtostderr=true)
-v, --v Level number for the log level verbosity
Use "kubeadm init [command] --help" for more information about a command.
源码
cmd := &cobra.Command{
Use: "init", // 传入init命令
Short: "Run this command in order to set up the Kubernetes control plane",
RunE: func(cmd *cobra.Command, args []string) error {
c, err := initRunner.InitData(args)
if err != nil {
return err
}
data, ok := c.(*initData)
if !ok {
return errors.New("invalid data struct")
}
// 打印kubernetes版本
fmt.Printf("[init] Using Kubernetes version: %s\n", data.cfg.KubernetesVersion)
return initRunner.Run(args)
},
Args: cobra.NoArgs,
}
AddInitConfigFlags 相关解析
AddClusterConfigFlags 相关解析
在帮助提示中对应如下参数
kubeadm init工作流
当执行kubeadm init的时候,会执行如下的工作流,后面我们会逐步分析各个阶段
phases.NewPreflightPhase()阶段
该步骤是在做出变更前进行一系列的预检查来验证系统状态,一些检查项目仅仅触发告警,其他的则会被视为错误并且退出kubeadm,除非问题得到解决或者用户指定了 --ignore-preflight-errors参数
var (
preflightExample = cmdutil.Examples(`
# Run pre-flight checks for kubeadm init using a config file.
kubeadm init phase preflight --config kubeadm-config.yaml
`)
)
// NewPreflightPhase creates a kubeadm workflow phase that implements preflight checks for a new control-plane node.
func NewPreflightPhase() workflow.Phase {
return workflow.Phase{
Name: "preflight",
Short: "Run pre-flight checks",
Long: "Run pre-flight checks for kubeadm init.",
Example: preflightExample,
Run: runPreflight,
InheritFlags: []string{
options.CfgPath,
options.ImageRepository,
options.NodeCRISocket,
options.IgnorePreflightErrors,
options.DryRun,
},
}
}
// runPreflight executes preflight checks logic.
func runPreflight(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("preflight phase invoked with an invalid data struct")
}
fmt.Println("[preflight] Running pre-flight checks")
// 首先调用了RunInitNodeChecks进行节点检查
if err := preflight.RunInitNodeChecks(utilsexec.New(), data.Cfg(), data.IgnorePreflightErrors(), false, false); err != nil {
return err
}
if data.DryRun() {
fmt.Println("[preflight] Would pull the required images (like 'kubeadm config images pull')")
return nil
}
fmt.Println("[preflight] Pulling images required for setting up a Kubernetes cluster")
fmt.Println("[preflight] This might take a minute or two, depending on the speed of your internet connection")
fmt.Println("[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'")
// 然后拉取镜像
return preflight.RunPullImagesCheck(utilsexec.New(), data.Cfg(), data.IgnorePreflightErrors())
}
func InitNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguration, ignorePreflightErrors sets.Set[string], isSecondaryControlPlane bool, downloadCerts bool) ([]Checker, error) {
if !isSecondaryControlPlane {
// First, check if we're root separately from the other preflight checks and fail fast
if err := RunRootCheckOnly(ignorePreflightErrors); err != nil {
return nil, err
}
}
manifestsDir := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)
checks := []Checker{
// 对CPU数量进行检查
NumCPUCheck{NumCPU: kubeadmconstants.ControlPlaneNumCPU},
// Linux only
// TODO: support other OS, if control-plane is supported on it.
// 对内存大小进行检查
MemCheck{Mem: kubeadmconstants.ControlPlaneMem},
// K8s版本进行检查
KubernetesVersionCheck{KubernetesVersion: cfg.KubernetesVersion, KubeadmVersion: kubeadmversion.Get().GitVersion},
// 防火墙进行检查
FirewalldCheck{ports: []int{int(cfg.LocalAPIEndpoint.BindPort), kubeadmconstants.KubeletPort}},
PortOpenCheck{port: int(cfg.LocalAPIEndpoint.BindPort)},
PortOpenCheck{port: kubeadmconstants.KubeSchedulerPort},
PortOpenCheck{port: kubeadmconstants.KubeControllerManagerPort},
FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeAPIServer, manifestsDir)},
FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeControllerManager, manifestsDir)},
FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeScheduler, manifestsDir)},
FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, manifestsDir)},
HTTPProxyCheck{Proto: "https", Host: cfg.LocalAPIEndpoint.AdvertiseAddress},
}
CPU和内存的一些默认配置如下
Node检查完后进行了拉取镜像的检查
// RunPullImagesCheck will pull images kubeadm needs if they are not found on the system
func RunPullImagesCheck(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguration, ignorePreflightErrors sets.Set[string]) error {
// 判断容器运行时是否正常
containerRuntime, err := utilruntime.NewContainerRuntime(utilsexec.New(), cfg.NodeRegistration.CRISocket)
if err != nil {
return &Error{Msg: err.Error()}
}
serialPull := true
if cfg.NodeRegistration.ImagePullSerial != nil {
serialPull = *cfg.NodeRegistration.ImagePullSerial
}
checks := []Checker{
ImagePullCheck{
runtime: containerRuntime,
imageList: images.GetControlPlaneImages(&cfg.ClusterConfiguration),
sandboxImage: images.GetPauseImage(&cfg.ClusterConfiguration),
imagePullPolicy: cfg.NodeRegistration.ImagePullPolicy,
imagePullSerial: serialPull,
},
}
return RunChecks(checks, os.Stderr, ignorePreflightErrors)
}
phases.NewCertsPhase()阶段
生成一个自签名的CA证书来为集群中的每一个组件建立身份标识。用户可以通过将其放入--cert-dir 配置的证书目录中(默认为/etc/kubernetes/pki) 来提供他们自己的CA证书或者密钥。
证书解析
[root@node1 ~]# tree /etc/kubernetes/pki
/etc/kubernetes/pki
├── apiserver.crt #API服务器用来为HTTPS服务提供TLS证书
├── apiserver-etcd-client.crt # API服务器与etcd集群通信的客户端证书。
├── apiserver-etcd-client.key # 对应apiserver-etcd-client.crt的私钥。
├── apiserver.key #对应API服务器证书apiserver.crt的私钥
├── apiserver-kubelet-client.crt # API服务器用于与kubelet通信的客户端证书。
├── apiserver-kubelet-client.key # 对应apiserver-kubelet-client.crt的私钥。
├── ca.crt # 根CA证书,用于签发其它证书和验证其它证书。
├── ca.key # 根CA证书ca.crt对应的私钥。
├── etcd
│ ├── ca.crt # etcd集群CA证书,签发etcd证书。
│ ├── ca.key # etcd集群CA证书ca.crt对应的私钥。
│ ├── healthcheck-client.crt # etcd 健康检查探针用的证书
│ ├── healthcheck-client.key
│ ├── peer.crt # etcd节点间通信的TLS证书。
│ ├── peer.key # 与etcd/peer.crt对应的私钥。
│ ├── server.crt
│ └── server.key
├── front-proxy-ca.crt # API代理的CA证书。
├── front-proxy-ca.key # 对应front-proxy-ca.crt的私钥。
├── front-proxy-client.crt # 前端客户端的一个证书
├── front-proxy-client.key
├── sa.key # 服务帐号密钥。
└── sa.pub # 服务帐号公钥。
// NewCertsPhase returns the phase for the certs
func NewCertsPhase() workflow.Phase {
return workflow.Phase{
Name: "certs",
Short: "Certificate generation",
Phases: newCertSubPhases(),
Run: runCerts,
Long: cmdutil.MacroCommandLongDescription,
}
}
phases.NewKubeConfigPhase()阶段
生成4个配置文件, 将kubeconfig文件写入/etc/kubernetes目录以便kubelet、controller-manager和scheduler用来连接到API服务器,它们每一个都有自己的身份标识,同时生成一个名为admin.conf的独立的kubeconfig文件,用于管理操作。
各个文件的作用
admin.conf # kubernetes管理员添加或修改集群资源时使用的客户端配置文件。
controller-manager.conf # controller-manager组件的配置文件。controller-manager负责维护集群状态。
kubelet.conf # kubelet组件的配置文件。kubelet运行在每个节点上负责Pod生命周期管理。
scheduler.conf # scheduler组件的配置文件。scheduler负责决定Pod调度运行在哪个Node上。
// NewKubeConfigPhase creates a kubeadm workflow phase that creates all kubeconfig files necessary to establish the control plane and the admin kubeconfig file.
func NewKubeConfigPhase() workflow.Phase {
return workflow.Phase{
Name: "kubeconfig",
Short: "Generate all kubeconfig files necessary to establish the control plane and the admin kubeconfig file",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{
{
Name: "all", // all表示4个配置文件会一起创建出来
Short: "Generate all kubeconfig files",
InheritFlags: getKubeConfigPhaseFlags("all"),
RunAllSiblings: true,
},
// 生成各个配置文件
NewKubeConfigFilePhase(kubeadmconstants.AdminKubeConfigFileName),
NewKubeConfigFilePhase(kubeadmconstants.SuperAdminKubeConfigFileName),
NewKubeConfigFilePhase(kubeadmconstants.KubeletKubeConfigFileName),
NewKubeConfigFilePhase(kubeadmconstants.ControllerManagerKubeConfigFileName),
NewKubeConfigFilePhase(kubeadmconstants.SchedulerKubeConfigFileName),
},
Run: runKubeConfig,
}
}
// NewKubeConfigFilePhase creates a kubeadm workflow phase that creates a kubeconfig file.
func NewKubeConfigFilePhase(kubeConfigFileName string) workflow.Phase {
return workflow.Phase{
Name: kubeconfigFilePhaseProperties[kubeConfigFileName].name,
Short: kubeconfigFilePhaseProperties[kubeConfigFileName].short,
Long: fmt.Sprintf(kubeconfigFilePhaseProperties[kubeConfigFileName].long, kubeConfigFileName),
Run: runKubeConfigFile(kubeConfigFileName),
InheritFlags: getKubeConfigPhaseFlags(kubeConfigFileName),
}
}
配置文件名称保存在一个变量文件中
phases.NewEtcdPhase() 阶段
如果没有使用外部etcd的话,会为etcd生成一份静态Pod的配置文件
var (
etcdLocalExample = cmdutil.Examples(`
# Generates the static Pod manifest file for etcd, functionally
# equivalent to what is generated by kubeadm init.
kubeadm init phase etcd local
# Generates the static Pod manifest file for etcd using options
# read from a configuration file.
kubeadm init phase etcd local --config config.yaml
`)
)
// NewEtcdPhase creates a kubeadm workflow phase that implements handling of etcd.
func NewEtcdPhase() workflow.Phase {
phase := workflow.Phase{
Name: "etcd",
Short: "Generate static Pod manifest file for local etcd",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{
newEtcdLocalSubPhase(),
},
}
return phase
}
func newEtcdLocalSubPhase() workflow.Phase {
phase := workflow.Phase{
Name: "local",
Short: "Generate the static Pod manifest file for a local, single-node local etcd instance",
Example: etcdLocalExample,
Run: runEtcdPhaseLocal(),
InheritFlags: getEtcdPhaseFlags(),
}
return phase
}
func getEtcdPhaseFlags() []string {
flags := []string{
options.CertificatesDir,
options.CfgPath,
options.ImageRepository,
options.Patches,
options.DryRun,
}
return flags
}
func runEtcdPhaseLocal() func(c workflow.RunData) error {
return func(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("etcd phase invoked with an invalid data struct")
}
cfg := data.Cfg()
// Add etcd static pod spec only if external etcd is not configured
// 判断下有没有使用外部etcd
if cfg.Etcd.External == nil {
// creates target folder if doesn't exist already
if !data.DryRun() {
// Create the etcd data directory
if err := etcdutil.CreateDataDirectory(cfg.Etcd.Local.DataDir); err != nil {
return err
}
} else {
fmt.Printf("[etcd] Would ensure that %q directory is present\n", cfg.Etcd.Local.DataDir)
}
fmt.Printf("[etcd] Creating static Pod manifest for local etcd in %q\n", data.ManifestDir())
if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(data.ManifestDir(), data.PatchesDir(), cfg.NodeRegistration.Name, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, data.DryRun()); err != nil {
return errors.Wrap(err, "error creating local etcd static pod manifest file")
}
} else {
klog.V(1).Infoln("[etcd] External etcd mode. Skipping the creation of a manifest for local etcd")
}
return nil
}
}
phases.NewControlPlanePhase() 阶段
该阶段为控制节点创建静态Pod的目录/etc/kubernetes/manifests以及在该目录下生成静态Pod所使用所使用的yaml文件
var (
controlPlaneExample = cmdutil.Examples(`
# Generates all static Pod manifest files for control plane components,
# functionally equivalent to what is generated by kubeadm init.
kubeadm init phase control-plane all
# Generates all static Pod manifest files using options read from a configuration file.
kubeadm init phase control-plane all --config config.yaml
`)
// 三个文件
controlPlanePhaseProperties = map[string]struct {
name string
short string
}{
kubeadmconstants.KubeAPIServer: {
name: "apiserver",
short: getPhaseDescription(kubeadmconstants.KubeAPIServer),
},
kubeadmconstants.KubeControllerManager: {
name: "controller-manager",
short: getPhaseDescription(kubeadmconstants.KubeControllerManager),
},
kubeadmconstants.KubeScheduler: {
name: "scheduler",
short: getPhaseDescription(kubeadmconstants.KubeScheduler),
},
}
)
func getPhaseDescription(component string) string {
return fmt.Sprintf("Generates the %s static Pod manifest", component)
}
// NewControlPlanePhase creates a kubeadm workflow phase that implements bootstrapping the control plane.
func NewControlPlanePhase() workflow.Phase {
phase := workflow.Phase{
Name: "control-plane",
Short: "Generate all static Pod manifest files necessary to establish the control plane",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{
{
Name: "all",
Short: "Generate all static Pod manifest files",
InheritFlags: getControlPlanePhaseFlags("all"),
Example: controlPlaneExample,
RunAllSiblings: true,
},
newControlPlaneSubphase(kubeadmconstants.KubeAPIServer),
newControlPlaneSubphase(kubeadmconstants.KubeControllerManager),
newControlPlaneSubphase(kubeadmconstants.KubeScheduler),
},
Run: runControlPlanePhase,
}
return phase
}
func newControlPlaneSubphase(component string) workflow.Phase {
phase := workflow.Phase{
Name: controlPlanePhaseProperties[component].name,
Short: controlPlanePhaseProperties[component].short,
Run: runControlPlaneSubphase(component),
InheritFlags: getControlPlanePhaseFlags(component),
}
return phase
}
func getControlPlanePhaseFlags(name string) []string {
flags := []string{
options.CfgPath,
options.CertificatesDir,
options.KubernetesVersion,
options.ImageRepository,
options.Patches,
options.DryRun,
}
if name == "all" || name == kubeadmconstants.KubeAPIServer {
flags = append(flags,
options.APIServerAdvertiseAddress,
options.ControlPlaneEndpoint,
options.APIServerBindPort,
options.APIServerExtraArgs,
options.FeatureGatesString,
options.NetworkingServiceSubnet,
)
}
if name == "all" || name == kubeadmconstants.KubeControllerManager {
flags = append(flags,
options.ControllerManagerExtraArgs,
options.NetworkingPodSubnet,
)
}
if name == "all" || name == kubeadmconstants.KubeScheduler {
flags = append(flags,
options.SchedulerExtraArgs,
)
}
return flags
}
func runControlPlanePhase(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("control-plane phase invoked with an invalid data struct")
}
fmt.Printf("[control-plane] Using manifest folder %q\n", data.ManifestDir())
return nil
}
// 启动静态Pod
func runControlPlaneSubphase(component string) func(c workflow.RunData) error {
return func(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("control-plane phase invoked with an invalid data struct")
}
cfg := data.Cfg()
fmt.Printf("[control-plane] Creating static Pod manifest for %q\n", component)
return controlplane.CreateStaticPodFiles(data.ManifestDir(), data.PatchesDir(), &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, data.DryRun(), component)
}
}
phases.NewKubeletStartPhase() 阶段
- 该阶段将env的变量写入到/var/lib/kubelet/kubeadm-flags.env中
- 并将kubelet的配置写入到/var/lib/kubelet/config.yaml中
- 然后启动kubelet
var (
kubeletStartPhaseExample = cmdutil.Examples(`
# Writes a dynamic environment file with kubelet flags from a InitConfiguration file.
kubeadm init phase kubelet-start --config config.yaml
`)
)
// NewKubeletStartPhase creates a kubeadm workflow phase that start kubelet on a node.
func NewKubeletStartPhase() workflow.Phase {
return workflow.Phase{
Name: "kubelet-start",
Short: "Write kubelet settings and (re)start the kubelet",
Long: "Write a file with KubeletConfiguration and an environment file with node specific kubelet settings, and then (re)start kubelet.",
Example: kubeletStartPhaseExample,
Run: runKubeletStart,
InheritFlags: []string{
options.CfgPath,
options.ImageRepository,
options.NodeCRISocket,
options.NodeName,
options.Patches,
options.DryRun,
},
}
}
// runKubeletStart executes kubelet start logic.
func runKubeletStart(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("kubelet-start phase invoked with an invalid data struct")
}
// First off, configure the kubelet. In this short timeframe, kubeadm is trying to stop/restart the kubelet
// Try to stop the kubelet service so no race conditions occur when configuring it
if !data.DryRun() {
klog.V(1).Infoln("Stopping the kubelet")
kubeletphase.TryStopKubelet()
}
// 将env写入到文件中
// Write env file with flags for the kubelet to use. We do not need to write the --register-with-taints for the control-plane,
// as we handle that ourselves in the mark-control-plane phase
// TODO: Maybe we want to do that some time in the future, in order to remove some logic from the mark-control-plane phase?
if err := kubeletphase.WriteKubeletDynamicEnvFile(&data.Cfg().ClusterConfiguration, &data.Cfg().NodeRegistration, false, data.KubeletDir()); err != nil {
return errors.Wrap(err, "error writing a dynamic environment file for the kubelet")
}
// Write the kubelet configuration file to disk.
// kubelet的配置文件写入到磁盘
if err := kubeletphase.WriteConfigToDisk(&data.Cfg().ClusterConfiguration, data.KubeletDir(), data.PatchesDir(), data.OutputWriter()); err != nil {
return errors.Wrap(err, "error writing kubelet configuration to disk")
}
// Try to start the kubelet service in case it's inactive
// 启动kubelet
if !data.DryRun() {
fmt.Println("[kubelet-start] Starting the kubelet")
kubeletphase.TryStartKubelet()
}
return nil
}
phases.NewWaitControlPlanePhase() 阶段
kubelet 会监视/etc/kubernetes/manifests,以便在启动时候创建Pod,一旦控制平面的Pod都运行起来,kubeadm init的工作流程会继续往下执行
var (
kubeletFailTempl = template.Must(template.New("init").Parse(dedent.Dedent(`
Unfortunately, an error has occurred:
{{ .Error }}
This error is likely caused by:
- The kubelet is not running
- The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)
If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
- 'systemctl status kubelet'
- 'journalctl -xeu kubelet'
Additionally, a control plane component may have crashed or exited when started by the container runtime.
To troubleshoot, list all containers using your preferred container runtimes CLI.
Here is one example how you may list all running Kubernetes containers by using crictl:
- 'crictl --runtime-endpoint {{ .Socket }} ps -a | grep kube | grep -v pause'
Once you have found the failing container, you can inspect its logs with:
- 'crictl --runtime-endpoint {{ .Socket }} logs CONTAINERID'
`)))
)
// NewWaitControlPlanePhase is a hidden phase that runs after the control-plane and etcd phases
func NewWaitControlPlanePhase() workflow.Phase {
phase := workflow.Phase{
Name: "wait-control-plane",
Short: "Wait for the control plane to start",
// TODO: unhide this phase once WaitForAllControlPlaneComponents goes GA:
// https://github.com/kubernetes/kubeadm/issues/2907
Hidden: true,
Run: runWaitControlPlanePhase,
}
return phase
}
func runWaitControlPlanePhase(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("wait-control-plane phase invoked with an invalid data struct")
}
// If we're dry-running, print the generated manifests.
// TODO: think of a better place to move this call - e.g. a hidden phase.
if data.DryRun() {
if err := dryrunutil.PrintFilesIfDryRunning(true /* needPrintManifest */, data.ManifestDir(), data.OutputWriter()); err != nil {
return errors.Wrap(err, "error printing files on dryrun")
}
}
// Both Wait* calls below use a /healthz endpoint, thus a client without permissions works fine
client, err := data.ClientWithoutBootstrap()
if err != nil {
return errors.Wrap(err, "cannot obtain client without bootstrap")
}
waiter, err := newControlPlaneWaiter(data.DryRun(), 0, client, data.OutputWriter())
if err != nil {
return errors.Wrap(err, "error creating waiter")
}
fmt.Printf("[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods"+
" from directory %q\n",
data.ManifestDir())
handleError := func(err error) error {
context := struct {
Error string
Socket string
}{
Error: fmt.Sprintf("%v", err),
Socket: data.Cfg().NodeRegistration.CRISocket,
}
kubeletFailTempl.Execute(data.OutputWriter(), context)
return errors.New("couldn't initialize a Kubernetes cluster")
}
waiter.SetTimeout(data.Cfg().Timeouts.KubeletHealthCheck.Duration)
if err := waiter.WaitForKubelet(); err != nil {
return handleError(err)
}
waiter.SetTimeout(data.Cfg().Timeouts.ControlPlaneComponentHealthCheck.Duration)
if features.Enabled(data.Cfg().ClusterConfiguration.FeatureGates, features.WaitForAllControlPlaneComponents) {
// 等待控制平面组件运行起来
err = waiter.WaitForControlPlaneComponents(&data.Cfg().ClusterConfiguration)
} else {
err = waiter.WaitForAPI()
}
if err != nil {
return handleError(err)
}
return nil
}
// newControlPlaneWaiter returns a new waiter that is used to wait on the control plane to boot up.
func newControlPlaneWaiter(dryRun bool, timeout time.Duration, client clientset.Interface, out io.Writer) (apiclient.Waiter, error) {
if dryRun {
return dryrunutil.NewWaiter(), nil
}
return apiclient.NewKubeWaiter(client, timeout, out), nil
}
phases.NewUploadConfigPhase() 阶段
将kubeadm-configmap以及kubelet-configmap上传到控制层面当中
var (
uploadKubeadmConfigLongDesc = fmt.Sprintf(cmdutil.LongDesc(`
Upload the kubeadm ClusterConfiguration to a ConfigMap called %s in the %s namespace.
This enables correct configuration of system components and a seamless user experience when upgrading.
Alternatively, you can use kubeadm config.
`), kubeadmconstants.KubeadmConfigConfigMap, metav1.NamespaceSystem)
uploadKubeadmConfigExample = cmdutil.Examples(`
# upload the configuration of your cluster
kubeadm init phase upload-config --config=myConfig.yaml
`)
uploadKubeletConfigLongDesc = cmdutil.LongDesc(`
Upload the kubelet configuration extracted from the kubeadm InitConfiguration object
to a kubelet-config ConfigMap in the cluster
`)
uploadKubeletConfigExample = cmdutil.Examples(`
# Upload the kubelet configuration from the kubeadm Config file to a ConfigMap in the cluster.
kubeadm init phase upload-config kubelet --config kubeadm.yaml
`)
)
// NewUploadConfigPhase returns the phase to uploadConfig
func NewUploadConfigPhase() workflow.Phase {
return workflow.Phase{
Name: "upload-config",
Aliases: []string{"uploadconfig"},
Short: "Upload the kubeadm and kubelet configuration to a ConfigMap",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{
{
Name: "all",
Short: "Upload all configuration to a config map",
RunAllSiblings: true,
InheritFlags: getUploadConfigPhaseFlags(),
},
{
Name: "kubeadm",
Short: "Upload the kubeadm ClusterConfiguration to a ConfigMap",
Long: uploadKubeadmConfigLongDesc,
Example: uploadKubeadmConfigExample,
Run: runUploadKubeadmConfig,
InheritFlags: getUploadConfigPhaseFlags(),
},
{
Name: "kubelet",
Short: "Upload the kubelet component config to a ConfigMap",
Long: uploadKubeletConfigLongDesc,
Example: uploadKubeletConfigExample,
Run: runUploadKubeletConfig,
InheritFlags: getUploadConfigPhaseFlags(),
},
},
}
}
func getUploadConfigPhaseFlags() []string {
return []string{
options.CfgPath,
options.NodeCRISocket,
options.KubeconfigPath,
options.DryRun,
}
}
// runUploadKubeadmConfig uploads the kubeadm configuration to a ConfigMap
func runUploadKubeadmConfig(c workflow.RunData) error {
cfg, client, err := getUploadConfigData(c)
if err != nil {
return err
}
klog.V(1).Infoln("[upload-config] Uploading the kubeadm ClusterConfiguration to a ConfigMap")
if err := uploadconfig.UploadConfiguration(cfg, client); err != nil {
return errors.Wrap(err, "error uploading the kubeadm ClusterConfiguration")
}
return nil
}
// runUploadKubeletConfig uploads the kubelet configuration to a ConfigMap
func runUploadKubeletConfig(c workflow.RunData) error {
cfg, client, err := getUploadConfigData(c)
if err != nil {
return err
}
klog.V(1).Infoln("[upload-config] Uploading the kubelet component config to a ConfigMap")
if err = kubeletphase.CreateConfigMap(&cfg.ClusterConfiguration, client); err != nil {
return errors.Wrap(err, "error creating kubelet configuration ConfigMap")
}
klog.V(1).Infoln("[upload-config] Preserving the CRISocket information for the control-plane node")
if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil {
return errors.Wrap(err, "Error writing Crisocket information for the control-plane node")
}
return nil
}
func getUploadConfigData(c workflow.RunData) (*kubeadmapi.InitConfiguration, clientset.Interface, error) {
data, ok := c.(InitData)
if !ok {
return nil, nil, errors.New("upload-config phase invoked with an invalid data struct")
}
cfg := data.Cfg()
client, err := data.Client()
if err != nil {
return nil, nil, err
}
return cfg, client, err
}
phases.NewUploadCertsPhase() 阶段
此参数是我们在前面kubeadmin init --config=kubeadm-config.yaml --upload-certs增加upload-certs才会执行此动作,否则不会将kubeadm的certs上传上去。
// NewUploadCertsPhase returns the uploadCerts phase
func NewUploadCertsPhase() workflow.Phase {
return workflow.Phase{
Name: "upload-certs",
Short: fmt.Sprintf("Upload certificates to %s", kubeadmconstants.KubeadmCertsSecret),
Long: fmt.Sprintf("Upload control plane certificates to the %s Secret", kubeadmconstants.KubeadmCertsSecret),
Run: runUploadCerts,
InheritFlags: []string{
options.CfgPath,
options.KubeconfigPath,
options.UploadCerts,
options.CertificateKey,
options.SkipCertificateKeyPrint,
options.DryRun,
},
}
}
func runUploadCerts(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("upload-certs phase invoked with an invalid data struct")
}
if !data.UploadCerts() {
fmt.Printf("[upload-certs] Skipping phase. Please see --%s\n", options.UploadCerts)
return nil
}
client, err := data.Client()
if err != nil {
return err
}
if len(data.CertificateKey()) == 0 {
certificateKey, err := copycerts.CreateCertificateKey()
if err != nil {
return err
}
data.SetCertificateKey(certificateKey)
}
if err := copycerts.UploadCerts(client, data.Cfg(), data.CertificateKey()); err != nil {
return errors.Wrap(err, "error uploading certs")
}
if !data.SkipCertificateKeyPrint() {
fmt.Printf("[upload-certs] Using certificate key:\n%s\n", data.CertificateKey())
}
return nil
}
phases.NewMarkControlPlanePhase() 阶段
该阶段主要是对控制平面节点应用标签和污点标记以便不会在它上面运行其他工作负载。
ar (
markControlPlaneExample = cmdutil.Examples(`
# Applies control-plane label and taint to the current node, functionally equivalent to what executed by kubeadm init.
kubeadm init phase mark-control-plane --config config.yaml
# Applies control-plane label and taint to a specific node
kubeadm init phase mark-control-plane --node-name myNode
`)
)
// NewMarkControlPlanePhase creates a kubeadm workflow phase that implements mark-controlplane checks.
func NewMarkControlPlanePhase() workflow.Phase {
return workflow.Phase{
Name: "mark-control-plane",
Short: "Mark a node as a control-plane",
Example: markControlPlaneExample,
InheritFlags: []string{
options.NodeName,
options.CfgPath,
options.DryRun,
},
Run: runMarkControlPlane,
}
}
// runMarkControlPlane executes mark-control-plane checks logic.
func runMarkControlPlane(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("mark-control-plane phase invoked with an invalid data struct")
}
client, err := data.Client()
if err != nil {
return err
}
nodeRegistration := data.Cfg().NodeRegistration
return markcontrolplanephase.MarkControlPlane(client, nodeRegistration.Name, nodeRegistration.Taints)
}
phases.NewBootstrapTokenPhase() 阶段
配置引导使用的token,用于添加Node节点等
var (
bootstrapTokenLongDesc = cmdutil.LongDesc(`
Bootstrap tokens are used for establishing bidirectional trust between a node joining
the cluster and a control-plane node.
This command makes all the configurations required to make bootstrap tokens works
and then creates an initial token.
`)
bootstrapTokenExamples = cmdutil.Examples(`
# Make all the bootstrap token configurations and create an initial token, functionally
# equivalent to what generated by kubeadm init.
kubeadm init phase bootstrap-token
`)
)
// NewBootstrapTokenPhase returns the phase to bootstrapToken
func NewBootstrapTokenPhase() workflow.Phase {
return workflow.Phase{
Name: "bootstrap-token",
Aliases: []string{"bootstraptoken"},
Short: "Generates bootstrap tokens used to join a node to a cluster",
Example: bootstrapTokenExamples,
Long: bootstrapTokenLongDesc,
InheritFlags: []string{
options.CfgPath,
options.KubeconfigPath,
options.SkipTokenPrint,
options.DryRun,
},
Run: runBootstrapToken,
}
}
func runBootstrapToken(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("bootstrap-token phase invoked with an invalid data struct")
}
client, err := data.Client()
if err != nil {
return err
}
if !data.SkipTokenPrint() {
tokens := data.Tokens()
if len(tokens) == 1 {
fmt.Printf("[bootstrap-token] Using token: %s\n", tokens[0])
} else if len(tokens) > 1 {
fmt.Printf("[bootstrap-token] Using tokens: %v\n", tokens)
}
}
fmt.Println("[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles")
// Create the default node bootstrap token
if err := nodebootstraptokenphase.UpdateOrCreateTokens(client, false, data.Cfg().BootstrapTokens); err != nil {
return errors.Wrap(err, "error updating or creating token")
}
// Create RBAC rules that makes the bootstrap tokens able to get nodes
if err := nodebootstraptokenphase.AllowBoostrapTokensToGetNodes(client); err != nil {
return errors.Wrap(err, "error allowing bootstrap tokens to get Nodes")
}
// Create RBAC rules that makes the bootstrap tokens able to post CSRs
if err := nodebootstraptokenphase.AllowBootstrapTokensToPostCSRs(client); err != nil {
return errors.Wrap(err, "error allowing bootstrap tokens to post CSRs")
}
// Create RBAC rules that makes the bootstrap tokens able to get their CSRs approved automatically
if err := nodebootstraptokenphase.AutoApproveNodeBootstrapTokens(client); err != nil {
return errors.Wrap(err, "error auto-approving node bootstrap tokens")
}
// Create/update RBAC rules that makes the nodes to rotate certificates and get their CSRs approved automatically
if err := nodebootstraptokenphase.AutoApproveNodeCertificateRotation(client); err != nil {
return err
}
// Create the cluster-info ConfigMap with the associated RBAC rules
if err := clusterinfophase.CreateBootstrapConfigMapIfNotExists(client, data.KubeConfigPath()); err != nil {
return errors.Wrap(err, "error creating bootstrap ConfigMap")
}
if err := clusterinfophase.CreateClusterInfoRBACRules(client); err != nil {
return errors.Wrap(err, "error creating clusterinfo RBAC rules")
}
return nil
}
phases.NewKubeletFinalizePhase()阶段
更新/etc/kubernetes/kubelet.conf指向可运行的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, options.DryRun},
Example: kubeletFinalizePhaseExample,
RunAllSiblings: true,
},
{
Name: "experimental-cert-rotation",
Short: "Enable kubelet client certificate rotation", // 启用kubelet客户端证书轮换,作用是提高集群安全性,自动续期证书
InheritFlags: []string{options.CfgPath, options.CertificatesDir, options.DryRun},
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, idx := kubeadmapi.GetArgValue(cfg.NodeRegistration.KubeletExtraArgs, "cert-dir", -1)
if idx > -1 {
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.
if len(kubeconfig.CurrentContext) == 0 {
return errors.Errorf("the file %q does not have current context set", kubeconfigPath)
}
currentContext, ok := kubeconfig.Contexts[kubeconfig.CurrentContext]
if !ok {
return errors.Errorf("the file %q is not a valid kubeconfig: %q set as current-context, but not found in context list", kubeconfigPath, kubeconfig.CurrentContext)
}
userName := currentContext.AuthInfo
if len(userName) == 0 {
return errors.Errorf("the file %q is not a valid kubeconfig: empty username for current context", kubeconfigPath)
}
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
}
phases.NewAddonPhase()阶段
该阶段进行coredns以及kube-proxy的安装
该阶段调用两个接口去获取coredns和kube-proxy
- runCoreDNSAddon 将coreDNS插件安装到集群
- runKubeProxyAddon将kube-proxy插件安装到集群
ar (
coreDNSAddonLongDesc = cmdutil.LongDesc(`
Install the CoreDNS addon components via the API server.
Please note that although the DNS server is deployed, it will not be scheduled until CNI is installed.
`)
kubeProxyAddonLongDesc = cmdutil.LongDesc(`
Install the kube-proxy addon components via the API server.
`)
printManifest = false
)
// NewAddonPhase returns the addon Cobra command
func NewAddonPhase() workflow.Phase {
dnsLocalFlags := pflag.NewFlagSet(options.PrintManifest, pflag.ContinueOnError)
dnsLocalFlags.BoolVar(&printManifest, options.PrintManifest, printManifest, "Print the addon manifests to STDOUT instead of installing them")
proxyLocalFlags := pflag.NewFlagSet(options.PrintManifest, pflag.ContinueOnError)
proxyLocalFlags.BoolVar(&printManifest, options.PrintManifest, printManifest, "Print the addon manifests to STDOUT instead of installing them")
return workflow.Phase{
Name: "addon",
Short: "Install required addons for passing conformance tests",
Long: cmdutil.MacroCommandLongDescription,
Phases: []workflow.Phase{
{
Name: "all",
Short: "Install all the addons",
InheritFlags: getAddonPhaseFlags("all"),
RunAllSiblings: true,
},
{
Name: "coredns",
Short: "Install the CoreDNS addon to a Kubernetes cluster",
Long: coreDNSAddonLongDesc,
InheritFlags: getAddonPhaseFlags("coredns"),
Run: runCoreDNSAddon,
LocalFlags: dnsLocalFlags,
},
{
Name: "kube-proxy",
Short: "Install the kube-proxy addon to a Kubernetes cluster",
Long: kubeProxyAddonLongDesc,
InheritFlags: getAddonPhaseFlags("kube-proxy"),
Run: runKubeProxyAddon,
LocalFlags: proxyLocalFlags,
},
},
}
}
func getInitData(c workflow.RunData) (*kubeadmapi.InitConfiguration, clientset.Interface, io.Writer, error) {
data, ok := c.(InitData)
if !ok {
return nil, nil, nil, errors.New("addon phase invoked with an invalid data struct")
}
cfg := data.Cfg()
var client clientset.Interface
var err error
if !printManifest {
client, err = data.Client()
if err != nil {
return nil, nil, nil, err
}
}
out := data.OutputWriter()
return cfg, client, out, err
}
// runCoreDNSAddon installs CoreDNS addon to a Kubernetes cluster
func runCoreDNSAddon(c workflow.RunData) error {
cfg, client, out, err := getInitData(c)
if err != nil {
return err
}
// 将CoreDNS插件安装到k8s集群
return dnsaddon.EnsureDNSAddon(&cfg.ClusterConfiguration, client, out, printManifest)
}
// runKubeProxyAddon installs KubeProxy addon to a Kubernetes cluster
func runKubeProxyAddon(c workflow.RunData) error {
cfg, client, out, err := getInitData(c)
if err != nil {
return err
}
// 将KubeProxy插件安装到k8s集群
return proxyaddon.EnsureProxyAddon(&cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, client, out, printManifest)
}
func getAddonPhaseFlags(name string) []string {
flags := []string{
options.CfgPath,
options.KubeconfigPath,
options.KubernetesVersion,
options.ImageRepository,
options.DryRun,
}
if name == "all" || name == "kube-proxy" {
flags = append(flags,
options.APIServerAdvertiseAddress,
options.ControlPlaneEndpoint,
options.APIServerBindPort,
options.NetworkingPodSubnet,
)
}
if name == "all" || name == "coredns" {
flags = append(flags,
options.FeatureGatesString,
options.NetworkingDNSDomain,
options.NetworkingServiceSubnet,
)
}
return flags
}
phases.NewShowJoinCommandPhase() 阶段
输出成功信息,包含一些可以加入集群节点的命令
var (
initDoneTempl = template.Must(template.New("init").Parse(dedent.Dedent(`
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i {{.KubeConfigPath}} $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
{{if .ControlPlaneEndpoint -}}
{{if .UploadCerts -}}
You can now join any number of the control-plane node running the following command on each as root:
{{.joinControlPlaneCommand}}
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
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
{{else -}}
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:
{{.joinControlPlaneCommand}}
{{end}}{{end}}Then you can join any number of worker nodes by running the following on each as root:
{{.joinWorkerCommand}}
`)))
)
// NewShowJoinCommandPhase creates a kubeadm workflow phase that implements showing the join command.
func NewShowJoinCommandPhase() workflow.Phase {
return workflow.Phase{
Name: "show-join-command",
Short: "Show the join command for control-plane and worker node",
Run: showJoinCommand,
Dependencies: []string{"bootstrap-token", "upload-certs"},
}
}
// showJoinCommand prints the join command after all the phases in init have finished
func showJoinCommand(c workflow.RunData) error {
data, ok := c.(InitData)
if !ok {
return errors.New("show-join-command phase invoked with an invalid data struct")
}
adminKubeConfigPath := data.KubeConfigPath()
// Prints the join command, multiple times in case the user has multiple tokens
for _, token := range data.Tokens() {
if err := printJoinCommand(data.OutputWriter(), adminKubeConfigPath, token, data); err != nil {
return errors.Wrap(err, "failed to print join command")
}
}
return nil
}
func printJoinCommand(out io.Writer, adminKubeConfigPath, token string, i InitData) error {
joinControlPlaneCommand, err := cmdutil.GetJoinControlPlaneCommand(adminKubeConfigPath, token, i.CertificateKey(), i.SkipTokenPrint(), i.SkipCertificateKeyPrint())
if err != nil {
return err
}
joinWorkerCommand, err := cmdutil.GetJoinWorkerCommand(adminKubeConfigPath, token, i.SkipTokenPrint())
if err != nil {
return err
}
ctx := map[string]interface{}{
"KubeConfigPath": adminKubeConfigPath,
"ControlPlaneEndpoint": i.Cfg().ControlPlaneEndpoint,
"UploadCerts": i.UploadCerts(),
"joinControlPlaneCommand": joinControlPlaneCommand,
"joinWorkerCommand": joinWorkerCommand,
}
return initDoneTempl.Execute(out, ctx)
}