kubeadmソース分析(kubernetesオフラインインストールパッケージ、3ステップインストール)
16537 ワード
k 8 sオフラインインストールパッケージ3ステップインストール、信じられないほど簡単
kubeadmソース分析
kubeadm生成証明書は/etc/kubernetes/pkiディレクトリの下で kubeadmはstatic pod yaml構成を生成し、すべて/etc/kubernetes/manifastsの下で kubeadmはkubelet構成を生成し、kubectl構成等は/etc/kubernetes下 である. kubeadm client goによるdns の起動
コードエントリ
Run関数を見つけて主なプロセスを分析します.
証明書が存在しない場合は証明書を作成しますので、証明書を/etc/kubernetes/pkiの下に置くことができます.以下、証明書が生成された場合を詳しく見てみましょう.
kubeconfigファイルの作成
manifestファイルを作成し、etcd apiserver manager schedulerがここで作成されています.プロファイルにetcdのアドレスが書かれている場合は、作成しないことがわかります.これにより、デフォルトの単点のetcdではなく、etcdクラスタを自分でインストールすることができます.役に立ちます.
APIserverとkubeletの起動が成功するのを待つと、ここでは私たちがよく遭遇するミラーが引っ張れないエラーに遭遇しますが、実はkubeletは別の理由でこのエラーを報告することもあります.ミラーが取れないと勘違いさせられます
masterにラベルを付けて、汚点をつけて、だからpodがmasterの上で汚点を取り除くことができることにスケジューリングしたいです
tockenの生成
クライアントgoを呼び出してdnsとkube-proxyを作成
筆者はコードの無脳的な流れを批判するが,筆者がインタフェースRenderConf Save Run Cleanなどに抽象化し,DNS kube-porxyや他のコンポーネントを実装し,dnsやkubeproxyの構成をレンダリングしなかったことが問題であり,static podではない可能性があり,join時のバグは後述する
ループはこの関数を呼び出して、私たちはその中の1、2つを見るだけでいいです.他のものは多くありません.
ルート証明書の生成:
k8s.io/client-go/util/certこのライブラリには、keyを生成する1つのcertを生成する2つの関数があります.
configには、他の証明書情報も入力できます.
秘密鍵はrsaライブラリにカプセル化された関数です.
自己署名証明書なので、ルート証明書にはCommonName情報しかありません.Organizationは設定されていないことに相当します.
生成されたらファイルに書き込みます.
ここでpemライブラリを呼び出して符号化しました
次にapiserverの証明書生成を見てみましょう.
この場合、AltNamesに注目することが重要です.masterにアクセスする必要があるすべてのアドレスドメイン名を追加する必要があります.対応するプロファイルのapiServerCertSAnsフィールドは、ルート証明書と変わりません.
これらのファイルが作成されていることがわかります.
k 8 sは2つのレンダリング構成の関数をカプセル化しています.違いは、dashboardに入るにはtokenが必要かどうか、apiを呼び出すにはtokenが必要かどうかです.token付きの構成で生成されたconfファイルを生成してください.基本的にはClientNameなどのものが異なるので、暗号化された証明書も異なり、ClientNameは証明書に暗号化され、k 8 sがユーザーが使用するときに取り出します.
だからポイントが来て、私たちが多くのテナントを作るときもこのように生成しなければなりません.その後、テナントにロールをバインドします.
それからConfig構造体を埋めて、最後にファイルに書きます.
ここではapiserver manager schedulerのpod構造体を返し、
etcdを作成するのと同じように、くだらないことはありません.
このエラーは非常に簡単に発生します.これを見ると、基本的にkubeletが起きていないことを確認する必要があります.selinux swapとCgroup driverが一致しているかどうかを確認する必要があります.selinux swapとCgroup driverが一致しているかどうかを確認する必要があります.setenforce 0&&swapoff-a&&systemctl restart kubelet
ここでcoreDNSを見つけた
そしてcoreDNSのyaml構成テンプレートは直接コードに書かれています:/app/phases/addons/dns/manifessts.go
それからテンプレートをレンダリングして、最後にk 8 sapiを呼び出して作成して、このような作成の方式は少し拙いですが、この地方の書くのはkubectlに及ばないほうがいいです
ここで特筆すべきはkubeproxyのconfigmapは本当にapiserverアドレスを入力すべきで、カスタマイズを許可して、高い利用可能な時に仮想ipを指定する必要があるため、修正しなければならなくて、面倒なkubeproxyは大きく悪くなくて、言わないで、変えたいなら変更します:app/phases/addons/proxy/manifest.go
kubeadm joinは比較的簡単で、cluster infoを取得し、kubeconfigを作成し、どのようにkubeinitを作成するかは一言ではっきり言えます.tokenを持ってkubeadmに権限を持って引き出すことができます
CreateWithTokenは前述したように、kubeletプロファイルを生成し、kubeletを起動すればよい.
kubeadm joinの問題は、構成をレンダリングするときにコマンドラインから送信されたapiserverアドレスを使用しないでclusterinfoのアドレスを使用することです.これは私たちが高可用性を行うのに役立ちません.仮想ipを送信するかもしれませんが、構成内はapiserのアドレスです.
kubeadmソース分析
正直に言うと、kubeadmのコードは真心が普通で、品質はあまり高くありません.
いくつかのポイントはまずkubeadmがやったいくつかの核心的なことを話します.
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のアドレスです.