kubeadmソース分析(kubernetesオフラインインストールパッケージ、3ステップインストール)


k 8 sオフラインインストールパッケージ3ステップインストール、信じられないほど簡単

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関数を見つけて主なプロセスを分析します.
    証明書が存在しない場合は証明書を作成しますので、証明書を/etc/kubernetes/pkiの下に置くことができます.以下、証明書が生成された場合を詳しく見てみましょう.
        if res, _ := certsphase.UsingExternalCA(i.cfg); !res {
            if err := certsphase.CreatePKIAssets(i.cfg); err != nil {
                return err
            }

    kubeconfigファイルの作成
            if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeConfigDir, i.cfg); err != nil {
                return err
            }

    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)
        }
    }

    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")
    }

    masterにラベルを付けて、汚点をつけて、だからpodがmasterの上で汚点を取り除くことができることにスケジューリングしたいです
    if err := markmasterphase.MarkMaster(client, i.cfg.NodeName); err != nil {
        return fmt.Errorf("error marking master: %v", err)
    }

    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)
    }

    クライアントgoを呼び出して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時のバグは後述する

    証明書の生成


    ループはこの関数を呼び出して、私たちはその中の1、2つを見るだけでいいです.他のものは多くありません.
    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を生成する1つのcertを生成する2つの関数があります.
    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},
    }

    k 8 sプロファイルの作成


    これらのファイルが作成されていることがわかります.
    return createKubeConfigFiles(
        outDir,
        cfg,
        kubeadmconstants.AdminKubeConfigFileName,
        kubeadmconstants.KubeletKubeConfigFileName,
        kubeadmconstants.ControllerManagerKubeConfigFileName,
        kubeadmconstants.SchedulerKubeConfigFileName,
    )

    k 8 sは2つのレンダリング構成の関数をカプセル化しています.違いは、dashboardに入るにはtokenが必要かどうか、apiを呼び出すにはtokenが必要かどうかです.token付きの構成で生成されたconfファイルを生成してください.基本的にはClientNameなどのものが異なるので、暗号化された証明書も異なり、ClientNameは証明書に暗号化され、k 8 sがユーザーが使用するときに取り出します.
    だからポイントが来て、私たちが多くのテナントを作るときもこのように生成しなければなりません.その後、テナントにロールをバインドします.
    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を作成するのと同じように、くだらないことはありません.

    クbeletの起動が成功するのを待つ


    このエラーは非常に簡単に発生します.これを見ると、基本的にkubeletが起きていないことを確認する必要があります.selinux swapとCgroup driverが一致しているかどうかを確認する必要があります.selinux swapとCgroup driverが一致しているかどうかを確認する必要があります.setenforce 0&&swapoff-a&&systemctl restart kubelet
    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 

    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/manifessts.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 }}
    ...

    それからテンプレートをレンダリングして、最後にk 8 sapiを呼び出して作成して、このような作成の方式は少し拙いですが、この地方の書くのは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/manifest.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のアドレスです.