Kubernetes 9 月份推出了 1.4 的版本在这个版本中最招人眼球的就是它推出了 Kubeadm 部署工具。本文由才云云开源高级工程师唐继元从源码的角度来剖析 kubeadm 的部署原理。


k8s version:1.4


Kubeadm app 支持的命令

从函数“NewKubeadmCommand”中可以看出:

cmds.AddCommand(NewCmdInit(out))

cmds.AddCommand(NewCmdJoin(out))

cmds.AddCommand(NewCmdVersion(out))

目前主要支持:“kubeadm init” 和 “kubeadm join” 命令。


kubeadm init

功能:“Run this in order to set up the Kubernetes master.”

主要的 options:

cmd.PersistentFlags().StringVar(

&cfg.Secrets.GivenToken, “token”, “”,

“Shared secret used to secure cluster bootstrap; if none is provided, one will be generated for you”,

)

cmd.PersistentFlags().StringSliceVar(

&cfg.API.AdvertiseAddresses, “api-advertise-addresses”, []string{},

“The IP addresses to advertise, in case autodetection fails”,

)

cmd.PersistentFlags().StringSliceVar(

&cfg.API.ExternalDNSNames, “api-external-dns-names”, []string{},

“The DNS names to advertise, in case you have configured them yourself”,

)

cmd.PersistentFlags().StringVar(

&cfg.Networking.ServiceSubnet, “service-cidr”, kubeadmapi.DefaultServicesSubnet,

“Use alterantive range of IP address for service VIPs”,

)

cmd.PersistentFlags().StringVar(

&cfg.Networking.PodSubnet, “pod-network-cidr”, “”,

“Specify range of IP addresses for the pod network; if set, the control plane will automatically allocate CIDRs for every node”,

)

cmd.PersistentFlags().StringVar(

&cfg.Networking.DNSDomain, “service-dns-domain”, kubeadmapi.DefaultServiceDNSDomain,

`Use alternative domain for services, e.g. “myorg.internal”`,

)

cmd.PersistentFlags().StringVar(

&cfg.CloudProvider, “cloud-provider”, “”,

`Enable cloud provider features (external load-balancers, storage, etc), e.g. “gce”`,

)

cmd.PersistentFlags().StringVar(

&cfg.KubernetesVersion, “use-kubernetes-version”, kubeadmapi.DefaultKubernetesVersion,

`Choose a specific Kubernetes version for the control plane`,

)

// …    这里忽略了未来将要Deprecated的flag


下面看看 init 命令到底做了些什么


// RunInit executes master node provisioning, including certificates, needed static pod manifests, etc.

func RunInit(out io.Writer, cmd *cobra.Command, args []string, cfg *kubeadmapi.MasterConfiguration) error {

// 如果未指定“api-advertise-addresses”,就选择默认路由对应interface的ip

// Auto-detect the IP

if len(cfg.API.AdvertiseAddresses) == 0 {

// TODO(phase1+) perhaps we could actually grab eth0 and eth1

ip, err := netutil.ChooseHostInterface()

if err != nil {

return err

}

cfg.API.AdvertiseAddresses = []string{ip.String()}

}

// 如果指定了“cloud-provider”,则初始化cloud provider,并向k8s注册

// TODO(phase1+) create a custom flag

if cfg.CloudProvider != “” {

if cloudprovider.IsCloudProvider(cfg.CloudProvider) {

fmt.Printf(“<cmd/init> cloud provider %q initialized for the control plane. Remember to set the same cloud provider flag on the kubelet./n”, cfg.CloudProvider)

} else {

return fmt.Errorf(“<cmd/init> cloud provider %q is not supported, you can use any of %v, or leave it unset./n”, cfg.CloudProvider, cloudprovider.CloudProviders())

}

}

// 如果未指定“token”,则自动创建一个token

// 然后将该token保存在“/etc/kubernetes/pki”目录下:tokens.csv

if err := kubemaster.CreateTokenAuthFile(&cfg.Secrets); err != nil {

return err

}

}

// 根据componentconfig的配置信息创建master节点上的static pod对象,

// 并将static pod以json文件格式保存到“/etc/kubernetes/manifests”目录下:

//     kube-apiserver.json  kube-controller-manager.json  kube-scheduler.json

// 如果用户未指定“external etcd”,则还会创建etcd static pod:etcd.json

if err := kubemaster.WriteStaticPodManifests(cfg); err != nil {

return err

}

// 基于token创建服务相关的key和setificate,保存在“/etc/kubernetes/pki”目录下:

//     ca-key.pem  ca-pub.pem ca.pem

//     apiserver-key.pem  apiserver-pub.pem apiserver.pem

//     sa-key.pem  sa-pub.pem

caKey, caCert, err := kubemaster.CreatePKIAssets(cfg)

if err != nil {

return err

}

// 创建client使用的certificate,并以conf文件保存在“/etc/kubernetes/pki”目录下:

//     admin.conf kubelet.conf

// admin.conf和kubelet.conf分别是kubectl和kubelet使用的kubeconfig

kubeconfigs, err := kubemaster.CreateCertsAndConfigForClients(cfg.API.AdvertiseAddresses, []string{“kubelet”, “admin”}, caKey, caCert)

if err != nil {

return err

}

for name, kubeconfig := range kubeconfigs {

if err := kubeadmutil.WriteKubeconfigIfNotExists(name, kubeconfig); err != nil {

return err

}

}

// 利用admin.conf创建client,并等待apiserver起来

// 使用这个client就相当于执行了如下命令:

//     kubectl –kubeconfig=”/etc/kubernetes/admin.conf” xxxx

// 后面的addons都是通过该client创建的

client, err := kubemaster.CreateClientAndWaitForAPI(kubeconfigs[“admin”])

if err != nil {

return err

}

// 设置默认不允许调度pod到master上的taint

// 这种情况下master上就只运行“/etc/kubernetes/manifests”目录下的static pod

// 和master上的一些addons(设置了toleration)

schedulePodsOnMaster := false

if err := kubemaster.UpdateMasterRoleLabelsAndTaints(client, schedulePodsOnMaster); err != nil {

return err

}

// 创建kube-discovery deployment和secret,并等待kube-discovery server起来

// kube-discovery server侦听9898端口

// kube-discovery是必须的addon组件

// 当需要往k8s集群添加node时,“kubeadm join”首先通过URL向kube-discovery server请求cluster的相关信息

//      http://MasterIP:9898/cluster-info/v1/?token-id=XXX.YYYY

// kube-discovery服务收到请求之后,将cluster ca证书,endpoint列表和token以k8s secrets的方式回复给发送请求的节点

if err := kubemaster.CreateDiscoveryDeploymentAndSecret(cfg, client, caCert); err != nil {

return err

}

// 创建其他必须的addon,主要是:kube-proxy, kube-dns

// kube-proxy 为daemonSet,而kube-dns为deployment

if err := kubemaster.CreateEssentialAddons(cfg, client); err != nil {

return err

}

}

从上面的分析可以看出:kubeadm init 主要负责创建 k8s 集群的 key、certs 和 conf 文件,创建 etcd、kube-apiserver、kube-controller-manager、kube-scheduler 这些 static pod 的 json 格式的 manifest 文件(kubelet 会监视“/etc/kubernetes/manifests”目录下的 manifest 文件,一旦发现则启动对应的 static pod),然后启动 kube-discovery deployment、kube-proxy daemonSet、kube-dns deployment 这三个 addon。

kubeadm join

kubeadm join –token <token> <master-ip>

功能:“Run this on any machine you wish to join an existing cluster.”,即将当前运行“kubeadm join”命令的节点加入到一个已经存在的 k8s 集群中。

主要的 options:

cmd.PersistentFlags().StringVar(

&cfg.Secrets.GivenToken, “token”, “”,

“(required) Shared secret used to secure bootstrap. Must match the output of ‘kubeadm init'”,

)

下面看看 join 命令到底做了些什么:


// RunJoin executes worked node provisioning and tries to join an existing cluster.

func RunJoin(out io.Writer, cmd *cobra.Command, args []string, s *kubeadmapi.NodeConfiguration) error {

// 用户必须要指定token

// 如果用户指定了token,则检查token的格式是否合法

// token格式要求(比如:f0c861.753c505740ecde4c):

//     1. 点分格式(2-part dot-separated format)

//     2. 前面部分为3个字节的tokenID,比如:f0c861

//     3. 后面部分为8个字节的token,比如:753c505740ecde4c

ok, err := kubeadmutil.UseGivenTokenIfValid(&s.Secrets)

if !ok {

if err != nil {

return fmt.Errorf(“<cmd/join> %v (see –help)/n”, err)

}

return fmt.Errorf(“Must specify –token (see –help)/n”)

}

// 1. 访问kube-discovery服务,获取cluster相关信息(cluster info对象,主要包括cluster ca证书,endpoint列表和token信息)

//     URL: http://MasterIP:9898/cluster-info/v1/?token-id=XXX.YYYY

// 2. 使用用户指定的token检验cluster info对象的签名,如果检验失败则自然不允许加入该k8s集群

clusterInfo, err := kubenode.RetrieveTrustedClusterInfo(s)

if err != nil {

return err

}

// 利用endpoint列表的其中一个apiserver的URL创建能与k8s master建立连接的ConnectionDetails对象

connectionDetails, err := kubenode.EstablishMasterConnection(s, clusterInfo)

if err != nil {

return err

}

// 通过ConnectionDetails对象与apiserver建立连接,并向apiserver请求为该节点创建证书,然后根据该证书创建kubeconfig对象

kubeconfig, err := kubenode.PerformTLSBootstrap(connectionDetails)

if err != nil {

return err

}

// 将kubeconfig对象写入“/etc/kubernetes/kubelet.config”文件

err = kubeadmutil.WriteKubeconfigIfNotExists(“kubelet”, kubeconfig)

if err != nil {

return err

}

}

然后我们看看 node 节点上 kubelet 是如何运行的:

/usr/bin/kubelet –kubeconfig=/etc/kubernetes/kubelet.conf –require-kubeconfig=true –pod-manifest-path=/etc/kubernetes/manifests –allow-privileged=true –network-plugin=cni –cni-conf-dir=/etc/cni/net.d –cni-bin-dir=/opt/cni/bin –cluster-dns=100.64.0.10 –cluster-domain=cluster.local

所以,节点上的 kubelet 需要通过 kubelet.conf 与 apiserver 进行安全通信。

从上面的分析可以看出:kubeadm join 主要负责创建 kubelet.conf 文件,创建的流程如下:

  1. 访问 kube-discovery 服务获取 k8s cluster info,主要包含如下信息:cluster ca 证书、endpoint 列表和 token

  2. 利用用户指定的 token,检验 k8s cluster info 的签名,建议通过才能进行后续步骤

  3. 与 endpoint 列表的其中一个 apiserver 建立连接,请求 apiserver 为该 node 节点创建证书,然后利用该获取到的证书创建 kubelet.conf

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐