k8s离线安装包 三步安装,简单到难以置信

kubeadm源码分析

说句实在话,kubeadm的代码写的真心一般,质量不是很高。

几个关键点来先说一下kubeadm干的几个核心的事:

  • kubeadm 生成证书在/etc/kubernetes/pki目录下
  • kubeadm 生成static pod yaml配置,全部在/etc/kubernetes/manifasts下
  • kubeadm 生成kubelet配置,kubectl配置等 在/etc/kubernetes下
  • kubeadm 通过client go去启动dns

kubeadm init

代码入口 cmd/kubeadm/app/cmd/init.go 建议大家去看看cobra

找到Run函数来分析下主要流程:

  1. 如果证书不存在,就创建证书,所以如果我们有自己的证书可以把它放在/etc/kubernetes/pki下即可, 下文细看如果生成证书
    if res, _ := certsphase.UsingExternalCA(i.cfg); !res {
        if err := certsphase.CreatePKIAssets(i.cfg); err != nil {
            return err
        }
  1. 创建kubeconfig文件
        if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeConfigDir, i.cfg); err != nil {
            return err
        }
  1. 创建manifest文件,etcd apiserver manager scheduler都在这里创建, 可以看到如果你的配置文件里已经写了etcd的地址了,就不创建了,这我们就可以自己装etcd集群,而不用默认单点的etcd,很有用
controlplanephase.CreateInitStaticPodManifestFiles(manifestDir, i.cfg);
if len(i.cfg.Etcd.Endpoints) == 0 {
    if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestDir, i.cfg); err != nil {
        return fmt.Errorf("error creating local etcd static pod manifest file: %v", err)
    }
}
  1. 等待APIserver和kubelet启动成功,这里就会遇到我们经常遇到的镜像拉不下来的错误,其实有时kubelet因为别的原因也会报这个错,让人误以为是镜像弄不下来
if err := waitForAPIAndKubelet(waiter); err != nil {
    ctx := map[string]string{
        "Error":                  fmt.Sprintf("%v", err),
        "APIServerImage":         images.GetCoreImage(kubeadmconstants.KubeAPIServer, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
        "ControllerManagerImage": images.GetCoreImage(kubeadmconstants.KubeControllerManager, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
        "SchedulerImage":         images.GetCoreImage(kubeadmconstants.KubeScheduler, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
    }

    kubeletFailTempl.Execute(out, ctx)

    return fmt.Errorf("couldn't initialize a Kubernetes cluster")
}
  1. 给master加标签,加污点, 所以想要pod调度到master上可以把污点清除了
if err := markmasterphase.MarkMaster(client, i.cfg.NodeName); err != nil {
    return fmt.Errorf("error marking master: %v", err)
}
  1. 生成tocken
if err := nodebootstraptokenphase.UpdateOrCreateToken(client, i.cfg.Token, false, i.cfg.TokenTTL.Duration, kubeadmconstants.DefaultTokenUsages, []string{kubeadmconstants.NodeBootstrapTokenAuthGroup}, tokenDescription); err != nil {
    return fmt.Errorf("error updating or creating token: %v", err)
}
  1. 调用clientgo创建dns和kube-proxy
if err := dnsaddonphase.EnsureDNSAddon(i.cfg, client); err != nil {
    return fmt.Errorf("error ensuring dns addon: %v", err)
}

if err := proxyaddonphase.EnsureProxyAddon(i.cfg, client); err != nil {
    return fmt.Errorf("error ensuring proxy addon: %v", err)
}

笔者批判代码无脑式的一个流程到底,要是笔者操刀定抽象成接口 RenderConf Save Run Clean等,DNS kube-porxy以及其它组件去实现,然后问题就是没把dns和kubeproxy的配置渲染出来,可能是它们不是static pod的原因, 然后就是join时的bug下文提到

证书生成

循环的调用了这一坨函数,我们只需要看其中一两个即可,其它的都差不多

certActions := []func(cfg *kubeadmapi.MasterConfiguration) error{
    CreateCACertAndKeyfiles,
    CreateAPIServerCertAndKeyFiles,
    CreateAPIServerKubeletClientCertAndKeyFiles,
    CreateServiceAccountKeyAndPublicKeyFiles,
    CreateFrontProxyCACertAndKeyFiles,
    CreateFrontProxyClientCertAndKeyFiles,
}

根证书生成:


//返回了根证书的公钥和私钥
func NewCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) {

    caCert, caKey, err := pkiutil.NewCertificateAuthority()
    if err != nil {
        return nil, nil, fmt.Errorf("failure while generating CA certificate and key: %v", err)
    }

    return caCert, caKey, nil
}

k8s.io/client-go/util/cert 这个库里面有两个函数,一个生成key的一个生成cert的:

key, err := certutil.NewPrivateKey()
config := certutil.Config{
    CommonName: "kubernetes",
}
cert, err := certutil.NewSelfSignedCACert(config, key)

config里面我们也可以填充一些别的证书信息:

type Config struct {
    CommonName   string
    Organization []string
    AltNames     AltNames
    Usages       []x509.ExtKeyUsage
}

私钥就是封装了rsa库里面的函数:

    "crypto/rsa"
    "crypto/x509"
func NewPrivateKey() (*rsa.PrivateKey, error) {
    return rsa.GenerateKey(cryptorand.Reader, rsaKeySize)
}

自签证书,所以根证书里只有CommonName信息,Organization相当于没设置:

func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, error) {
    now := time.Now()
    tmpl := x509.Certificate{
        SerialNumber: new(big.Int).SetInt64(0),
        Subject: pkix.Name{
            CommonName:   cfg.CommonName,
            Organization: cfg.Organization,
        },
        NotBefore:             now.UTC(),
        NotAfter:              now.Add(duration365d * 10).UTC(),
        KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
        BasicConstraintsValid: true,
        IsCA: true,
    }

    certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key)
    if err != nil {
        return nil, err
    }
    return x509.ParseCertificate(certDERBytes)
}

生成好之后把之写入文件:

 pkiutil.WriteCertAndKey(pkiDir, baseName, cert, key);
certutil.WriteCert(certificatePath, certutil.EncodeCertPEM(cert))

这里调用了pem库进行了编码

encoding/pem

func EncodeCertPEM(cert *x509.Certificate) []byte {
    block := pem.Block{
        Type:  CertificateBlockType,
        Bytes: cert.Raw,
    }
    return pem.EncodeToMemory(&block)
}

然后我们看apiserver的证书生成:

caCert, caKey, err := loadCertificateAuthorithy(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
//从根证书生成apiserver证书
apiCert, apiKey, err := NewAPIServerCertAndKey(cfg, caCert, caKey)

这时需要关注AltNames了比较重要,所有需要访问master的地址域名都得加进去,对应配置文件中apiServerCertSANs字段,其它东西与根证书无差别

config := certutil.Config{
    CommonName: kubeadmconstants.APIServerCertCommonName,
    AltNames:   *altNames,
    Usages:     []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}

创建k8s配置文件

可以看到创建了这些文件

return createKubeConfigFiles(
    outDir,
    cfg,
    kubeadmconstants.AdminKubeConfigFileName,
    kubeadmconstants.KubeletKubeConfigFileName,
    kubeadmconstants.ControllerManagerKubeConfigFileName,
    kubeadmconstants.SchedulerKubeConfigFileName,
)

k8s封装了两个渲染配置的函数:

区别是你的kubeconfig文件里会不会产生token,比如你进入dashboard需要一个token,或者你调用api需要一个token那么请生成带token的配置

生成的conf文件基本一直只是比如ClientName这些东西不同,所以加密后的证书也不同,ClientName会被加密到证书里,然后k8s取出来当用户使用

所以重点来了,我们做多租户时也要这样去生成。然后给该租户绑定角色。

return kubeconfigutil.CreateWithToken(
    spec.APIServer,
    "kubernetes",
    spec.ClientName,
    certutil.EncodeCertPEM(spec.CACert),
    spec.TokenAuth.Token,
), nil

return kubeconfigutil.CreateWithCerts(
    spec.APIServer,
    "kubernetes",
    spec.ClientName,
    certutil.EncodeCertPEM(spec.CACert),
    certutil.EncodePrivateKeyPEM(clientKey),
    certutil.EncodeCertPEM(clientCert),
), nil

然后就是填充Config结构体喽, 最后写到文件里,略

"k8s.io/client-go/tools/clientcmd/api
return &clientcmdapi.Config{
    Clusters: map[string]*clientcmdapi.Cluster{
        clusterName: {
            Server: serverURL,
            CertificateAuthorityData: caCert,
        },
    },
    Contexts: map[string]*clientcmdapi.Context{
        contextName: {
            Cluster:  clusterName,
            AuthInfo: userName,
        },
    },
    AuthInfos:      map[string]*clientcmdapi.AuthInfo{},
    CurrentContext: contextName,
}

创建static pod yaml文件

这里返回了apiserver manager scheduler的pod结构体,

specs := GetStaticPodSpecs(cfg, k8sVersion)
staticPodSpecs := map[string]v1.Pod{
    kubeadmconstants.KubeAPIServer: staticpodutil.ComponentPod(v1.Container{
        Name:          kubeadmconstants.KubeAPIServer,
        Image:         images.GetCoreImage(kubeadmconstants.KubeAPIServer, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
        Command:       getAPIServerCommand(cfg, k8sVersion),
        VolumeMounts:  staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer)),
        LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeAPIServer, int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS),
        Resources:     staticpodutil.ComponentResources("250m"),
        Env:           getProxyEnvVars(),
    }, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)),
    kubeadmconstants.KubeControllerManager: staticpodutil.ComponentPod(v1.Container{
        Name:          kubeadmconstants.KubeControllerManager,
        Image:         images.GetCoreImage(kubeadmconstants.KubeControllerManager, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
        Command:       getControllerManagerCommand(cfg, k8sVersion),
        VolumeMounts:  staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager)),
        LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeControllerManager, 10252, "/healthz", v1.URISchemeHTTP),
        Resources:     staticpodutil.ComponentResources("200m"),
        Env:           getProxyEnvVars(),
    }, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)),
    kubeadmconstants.KubeScheduler: staticpodutil.ComponentPod(v1.Container{
        Name:          kubeadmconstants.KubeScheduler,
        Image:         images.GetCoreImage(kubeadmconstants.KubeScheduler, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
        Command:       getSchedulerCommand(cfg),
        VolumeMounts:  staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler)),
        LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeScheduler, 10251, "/healthz", v1.URISchemeHTTP),
        Resources:     staticpodutil.ComponentResources("100m"),
        Env:           getProxyEnvVars(),
    }, mounts.GetVolumes(kubeadmconstants.KubeScheduler)),
}

//获取特定版本的镜像
func GetCoreImage(image, repoPrefix, k8sVersion, overrideImage string) string {
    if overrideImage != "" {
        return overrideImage
    }
    kubernetesImageTag := kubeadmutil.KubernetesVersionToImageTag(k8sVersion)
    etcdImageTag := constants.DefaultEtcdVersion
    etcdImageVersion, err := constants.EtcdSupportedVersion(k8sVersion)
    if err == nil {
        etcdImageTag = etcdImageVersion.String()
    }
    return map[string]string{
        constants.Etcd:                  fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "etcd", runtime.GOARCH, etcdImageTag),
        constants.KubeAPIServer:         fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-apiserver", runtime.GOARCH, kubernetesImageTag),
        constants.KubeControllerManager: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-controller-manager", runtime.GOARCH, kubernetesImageTag),
        constants.KubeScheduler:         fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-scheduler", runtime.GOARCH, kubernetesImageTag),
    }[image]
}
//然后就把这个pod写到文件里了,比较简单
 staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); 

创建etcd的一样,不多废话

等待kubelet启动成功

这个错误非常容易遇到,看到这个基本就是kubelet没起来,我们需要检查:selinux swap 和Cgroup driver是不是一致

setenforce 0 && swapoff -a && systemctl restart kubelet如果不行请保证 kubelet的Cgroup driver与docker一致,docker info|grep Cg

go func(errC chan error, waiter apiclient.Waiter) {
    // This goroutine can only make kubeadm init fail. If this check succeeds, it won't do anything special
    if err := waiter.WaitForHealthyKubelet(40*time.Second, "http://localhost:10255/healthz"); err != nil {
        errC <- err
    }
}(errorChan, waiter)

go func(errC chan error, waiter apiclient.Waiter) {
    // This goroutine can only make kubeadm init fail. If this check succeeds, it won't do anything special
    if err := waiter.WaitForHealthyKubelet(60*time.Second, "http://localhost:10255/healthz/syncloop"); err != nil {
        errC <- err
    }
}(errorChan, waiter)

创建DNS和kubeproxy

我就是在此发现coreDNS的

if features.Enabled(cfg.FeatureGates, features.CoreDNS) {
    return coreDNSAddon(cfg, client, k8sVersion)
}
return kubeDNSAddon(cfg, client, k8sVersion)

然后coreDNS的yaml配置模板直接是写在代码里的:

/app/phases/addons/dns/manifests.go

    CoreDNSDeployment = `
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: kube-dns
  template:
    metadata:
      labels:
        k8s-app: kube-dns
    spec:
      serviceAccountName: coredns
      tolerations:
      - key: CriticalAddonsOnly
        operator: Exists
      - key: {{ .MasterTaintKey }}
...

然后渲染模板,最后调用k8sapi创建,这种创建方式可以学习一下,虽然有点拙劣,这地方写的远不如kubectl好

coreDNSConfigMap := &v1.ConfigMap{}
if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), configBytes, coreDNSConfigMap); err != nil {
    return fmt.Errorf("unable to decode CoreDNS configmap %v", err)
}

// Create the ConfigMap for CoreDNS or update it in case it already exists
if err := apiclient.CreateOrUpdateConfigMap(client, coreDNSConfigMap); err != nil {
    return err
}

coreDNSClusterRoles := &rbac.ClusterRole{}
if err := kuberuntime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRole), coreDNSClusterRoles); err != nil {
    return fmt.Errorf("unable to decode CoreDNS clusterroles %v", err)
}
...

这里值得一提的是kubeproxy的configmap真应该把apiserver地址传入进来,允许自定义,因为做高可用时需要指定虚拟ip,得修改,很麻烦

kubeproxy大差不差,不说了,想改的话改: app/phases/addons/proxy/manifests.go

kubeadm join

kubeadm join比较简单,一句话就可以说清楚,获取cluster info, 创建kubeconfig,怎么创建的kubeinit里面已经说了。带上token让kubeadm有权限

可以拉取

return https.RetrieveValidatedClusterInfo(cfg.DiscoveryFile)

cluster info内容
type Cluster struct {
    // LocationOfOrigin indicates where this object came from.  It is used for round tripping config post-merge, but never serialized.
    LocationOfOrigin string
    // Server is the address of the kubernetes cluster (https://hostname:port).
    Server string `json:"server"`
    // InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
    // +optional
    InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
    // CertificateAuthority is the path to a cert file for the certificate authority.
    // +optional
    CertificateAuthority string `json:"certificate-authority,omitempty"`
    // CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
    // +optional
    CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
    // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
    // +optional
    Extensions map[string]runtime.Object `json:"extensions,omitempty"`
}

return kubeconfigutil.CreateWithToken(
    clusterinfo.Server,
    "kubernetes",
    TokenUser,
    clusterinfo.CertificateAuthorityData,
    cfg.TLSBootstrapToken,
), nil

CreateWithToken上文提到了不再赘述,这样就能去生成kubelet配置文件了,然后把kubelet启动起来即可

kubeadm join的问题就是渲染配置时没有使用命令行传入的apiserver地址,而用clusterinfo里的地址,这不利于我们做高可用,可能我们传入一个虚拟ip,但是配置里还是apiser的地址

扫码关注sealyun



探讨可加QQ群:98488045

kubeadm源码分析的更多相关文章

  1. kube-scheduler源码分析

    kubernetes集群三步安装 kube-scheduler源码分析 关于源码编译 我嫌弃官方提供的编译脚本太麻烦,所以用了更简单粗暴的方式编译k8s代码,当然官方脚本在编译所有项目或者夸平台编译以 ...

  2. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  3. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  4. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  5. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  6. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  7. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  8. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  9. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

随机推荐

  1. 使用vue-print-nb插件页面空白以及打印没有样式问题

    在使用vue-print-nb中遇到两个问题: 第一个问题:点击打印后,打印的内容是一片空白 vue-print-nb的原理大概是在你的页面上创建一个iframe,然后把你要打印的那一个div抓出来给 ...

  2. 2. 2.1查找命令——linux基础增强,Linux命令学习

    2.1.查找命令 grep命令 grep 命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并 把匹配的行打印出来. 格式: grep [option] pattern [file] 可使用 ...

  3. Vue技术点整理-安装引入

    Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架 所谓渐进式是指: 1,如果你有一个现成的web应用,你可以将vue作为该应用的一部分嵌入其中,带来更丰富的交互体验 ...

  4. c++学习书籍推荐《Beyond the C++ Standard Library》下载

    百度云及其他网盘下载地址:点我 作者简介 Björn Karlsson works as a Senior Software Engineer at ReadSoft, where he spends ...

  5. C# 使用XDocument实现读取、添加,修改XML文件

    新建xml文件编写如下内容做测试使用 需要引用:System.Xml.Linq 命名空间 一.读取XML 读取所有文档  筛选子元素为attribute1的元素,结果是IEumerable 通过Lin ...

  6. 分布式事务(4)---RocketMQ实现分布式事务项目

    RocketMQ实现分布式事务 有关RocketMQ实现分布式事务前面写了一篇博客 1.RocketMQ实现分布式事务原理 下面就这个项目做个整体简单介绍,并在文字最下方附上项目Github地址. 一 ...

  7. CAD2014学习笔记-文字编辑与尺寸标注

    基于 虎课网huke88.com CAD教程 文字与表格 输入文字:TEXT.MTEXT 插入表格:table 新建表格样式 尺寸标注 测量工具:Di.DLI 开启标注:打开工具-工具栏-标注 对齐/ ...

  8. dockerfile 制作镜像

    # Set the base image to UbuntuFROM ubuntu # File Author chenghanMAINTAINER chenghan ################ ...

  9. python介绍、安装及相关语法、python运维、编译与解释

    1.python介绍 Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/)是一种广泛使用的解释型.高级编程.通用型编程语言,由吉多.范罗苏姆创造,第一版发布于1991年.可以视 ...

  10. 通过自研数据库画像工具支持“去O”评估

    “去O”,是近些年来一直很火的一个话题,随之也产生了各种疑惑,包括现有数据库评估.技术选型等.去O是项系统工程,需要做好充分的评估.本文通过自研工具,生成数据库画像,为去O评估提供一手数据,希望给大家 ...