最新的 kubeadm 的设计文档在这里 ,如果把里面设计大概看一遍就能够理解里面的流程了,可以说设计的还是很缜密的,并且大大简化了 k8s 的运维工作。
kubeadm 安全设施 主要解释 kubeadm init 和 kubeadm join 的过程和实现
kubeadm init 
首先进行 preflight-checks ,检查系统是否满足初始化的状态。 
创建自签名的 CA,并且生成和签发各个 component 的私钥和证书 (/etc/kubernetes/pki),如果文件已存在就不会再生成了,比如要给 apiserver 添加域名可以重新签发一个证书,然后重启就好了。 
写入各个服务的配置文件,以及一个 admin.conf (/etc/kubernetes/) 
配置 kubelet 的动态配置加载     (disable by default) 
配置静态 pod  (/etc/kubernetes/manifests) 
给 master 添加 taint 和 label,让其他 pod 默认不会运行在 master 上 
生成用于让其他 kubelet 加入的 token 
配置用 token 加入的可以自动确认 CSR(也就是用 CA 自动签 kubelet 的证书) 
设置 kube-dns 
检查 self-hosting,如果设置了,就把 static pod 转成 daemonset 
 
是否使用外部 CA 的条件是,目录下有 CA 证书,但是没有 CA 私钥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if  res, _ := certsphase.UsingExternalCA(i.cfg); !res {                 if  err := certsphase.CreatePKIAssets(i.cfg); err != nil  {                 return  err         }                  if  err := kubeconfigphase.CreateInitKubeConfigFiles(kubeConfigDir, i.cfg); err != nil  {                 return  err         } } else  {         fmt.Println("[externalca] The file 'ca.key' was not found, yet all other certificates are present. Using external CA mode - certificates or kubeconfig will not be generated." ) } 
具体的生成 PKI 相关的配置的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func  CreatePKIAssets (cfg *kubeadmapi.MasterConfiguration) error  {        certActions := []func (cfg *kubeadmapi.MasterConfiguration) error {                 CreateCACertAndKeyFiles,                 CreateAPIServerCertAndKeyFiles,                 CreateAPIServerKubeletClientCertAndKeyFiles,                 CreateEtcdCACertAndKeyFiles,                 CreateEtcdServerCertAndKeyFiles,                 CreateEtcdPeerCertAndKeyFiles,                 CreateEtcdHealthcheckClientCertAndKeyFiles,                 CreateAPIServerEtcdClientCertAndKeyFiles,                 CreateServiceAccountKeyAndPublicKeyFiles,                 CreateFrontProxyCACertAndKeyFiles,                 CreateFrontProxyClientCertAndKeyFiles,         }         for  _, action := range  certActions {                 err := action(cfg)                 if  err != nil  {                         return  err                 }         }         fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n" , cfg.CertificatesDir)         return  nil  } 
列表中的函数都是用来生成所有证书和私钥的,主要依靠 k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil  来完成。私钥很容易生成,没什么需要特别配置的,主要看 CA 的证书里面配了啥,因为这个跟 k8s 的鉴权有关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func  NewSelfSignedCACert (cfg Config, key *rsa.PrivateKey) 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) } 
用 openssl x509 -in /etc/kubernetes/pki/ca.crt -text -noout 可以查看 ca 证书里面的内容,和我们看到的配置是一致的。
然后我们再看一下 apiserver 的私钥和证书是怎么签的,首先把生成的 CA 证书和私钥加载进来,然后生成私钥并且签出自己的证书。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func  CreateAPIServerCertAndKeyFiles (cfg *kubeadmapi.MasterConfiguration) error  {        caCert, caKey, err := loadCertificateAuthority(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)         if  err != nil  {                 return  err         }         apiCert, apiKey, err := NewAPIServerCertAndKey(cfg, caCert, caKey)         if  err != nil  {                 return  err         }         return  writeCertificateFilesIfNotExist(                 cfg.CertificatesDir,                 kubeadmconstants.APIServerCertAndKeyBaseName,                 caCert,                 apiCert,                 apiKey,         ) } 
这里比较重要,因为 SAN 这个是用来匹配域名的,如果这里没写好,HTTPS 是拒绝访问的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func  NewAPIServerCertAndKey (cfg *kubeadmapi.MasterConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) error ) {        altNames, err := pkiutil.GetAPIServerAltNames(cfg)         if  err != nil  {                 return  nil , nil , fmt.Errorf("failure while composing altnames for API server: %v" , err)         }         config := certutil.Config{                 CommonName: kubeadmconstants.APIServerCertCommonName,                 AltNames:   *altNames,                 Usages:     []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},         }         apiCert, apiKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)         if  err != nil  {                 return  nil , nil , fmt.Errorf("failure while creating API server key and certificate: %v" , err)         }         return  apiCert, apiKey, nil  } 
apiserver 的证书也是对的,马赛克的部分是我自己配的一些 IP 和 域名,和 kubeadm 配的一些 kube-dns 用的 overlay 网络上的 master 域名,其他 master 上的 components 也是类似的,在配高可用的时候要把每个 master 节点上的域名和 IP 都配上。kubelet 的证书也要配对,就在 join 里面介绍了,这个会用来鉴权 node 的身份。
其他部分的代码和设计文档的描述是一致的,所以没什么好看的,感觉一个好的设计文档是非常重要的,代码只是设计的实现,如果设计本身就可读性很强,阅读代码只是辅助理解一些细节和找 BUG 用的而已。
kubeadm join 
首先用 token 鉴权的方式获取 apiserver 的 CA 证书,并且通过 SHA256 验证。 
加载动态配置,如果 master 上有的话。 
TLS 初始化,首先用 token 鉴权的方式把自己的 CSR 发给 apiserver,然后签发自己的证书,这个证书要用来检查 node 的身份的。 
配置 kubelet 和 server 开始建立连接。 
 
新版本的 kubelet 有些变动,支持把一些 featuregate 配置写到文件里面,比如这个阻止 fork bomb  的配置,新版本只能用配置文件写了,不能通过参数配置。
kubeadm 的详细过程如下:
首先会把 flags 传入 NodeConfiguration 中,开始 AddJoinConfigFlags(cmd.PersistentFlags(),如果没有 nodeNamecfg 默认用 host 的 name 并且小写化通过 GetHostname获得,和在宿主机上执行 hostname 是一致的。在初始化之前先 尝试启动 TryStartKubelet 过程,然后把 token 和 server 写入(证书 data 是怎么生成的?),先配置kubelet-bootstrap-config
 整体流程如下,token 验证是基于 JWT 的,所以 token 的格式是 "^([a-z0-9]{6})\\.([a-z0-9]{16})$",分两部分,tokenID.tokenSecret,
1 2 3 4 5 6 7 8 discovery.For ->GetValidatedClusterInfoObject 这个是获取 CA 证书的过程      ->token.RetrieveValidatedClusterInfo 这一步是 JWT 验证          ->tokenutil.ParseToken   得到 tokenID 和 tokenSecret          ->pubKeyPins.Allow 加载用于检验 CA 证书的 HASH 值          ->buildInsecureBootstrapKubeConfig (用 token-bootstrap-client 身份)获取信息             -> 从kube-public 获取 configmap (和 kubectl describe configmap cluster-info -n kube-public 中的信息是一致的)这个configmap 里面包含 JWS 签名和 master 的 CA 证书,获取对应 tokenID 的 jws token,验证 token 成功,并且验证 CA 的证书 hash,如果通过说明这个 master 是可信的,然后拿到 CA 证书以后开始构建自己的证书,建立 secure config。          -> 
kubeconfigutil.WriteToDisk 会把 bootstrap-kubelet.conf 写入到配置目录中,kubeadm 的任务就完成了,之前的 kubeadm 会代替 kubelet 生成 kubelet.conf(其中用的是公钥鉴权),现在移走了,kubelet 启动的时候会尝试使用这个配置文件建立 HTTPS 的鉴权配置文件。
可以看一下 kubelet 用这个配置给 master 发 CSR 以后得到了什么,可以看到是生成了自己的证书的。
并且这个证书里面的 SAN 也是用来进行 Node 鉴权的身份确认的信息。用 openssl x509 -in /var/lib/kubelet/pki/kubelet-client.crt  -text -noout查看信息。
红线部分就是 Node 鉴权的信息,基于这个确认 node 的身份。另外一个是 Role Based Access Control,那个主要是配给 pod,限制 pod 行为用的,类似于 linux 的 chmod。
kubeadm 高可用配置 如果没有安全配置,其实高可用挺好配置的,现在加入了安全配置,虽然麻烦,但是还是能理解 k8s 的良苦用心的,理解了整个鉴权的过程以后就可以做,现在支持配置文件初始化其实更好配置了。可以基于这个文档配置 ,算了我还是不总结了,看懂上面的,照着配置就好了。主要是自己生成 CA,他们用的 cfssl 和 cfssljson,这个工具比 openssl 好用一点,比较容易配置。生成 ca 证书和私钥以后,就可以构建 HTTPS 的 etcd。
先创建 ca 的配置文件 ca-config.json。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 {     "signing" :  {          "default" :  {              "expiry" :  "43800h"          } ,          "profiles" :  {              "server" :  {                  "expiry" :  "43800h" ,                  "usages" :  [                      "signing" ,                      "key encipherment" ,                      "server auth" ,                      "client auth"                  ]              } ,              "client" :  {                  "expiry" :  "43800h" ,                  "usages" :  [                      "signing" ,                      "key encipherment" ,                      "client auth"                  ]              } ,              "peer" :  {                  "expiry" :  "43800h" ,                  "usages" :  [                      "signing" ,                      "key encipherment" ,                      "server auth" ,                      "client auth"                  ]              }          }      }  } 
然后生成用于自签名的 csr 的配置文件 ca-csr.json,用于签发自签名的 CA 证书。
1 2 3 4 5 6 7 {     "CN" :  "etcd" ,      "key" :  {          "algo" :  "rsa" ,          "size" :  2048      }  } 
结果就是目录下面生成了,ca-key.pem 和 ca.pem,这个命令不太按规则,对应的叫 ca.key 和 ca.crt,pem 是密钥保存的格式。
接下来用 client.json 获得自己的私钥和通过 ca 签的证书。
1 2 3 4 5 6 7 {     "CN" :  "client" ,      "key" :  {          "algo" :  "ecdsa" ,          "size" :  256      }  } 
执行 cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client,生成了 client-key.pem,client.pem,分别是私钥和证书,把文件 ca.pem,ca-key.pem, client.pem,client-key.pem,ca-config.json, 拷贝到每台 master 机器上面,一般放到 /etc/kubernetes/pki/ 下面。
然后
1 2 3 4 5 6 7 cfssl print-defaults csr > config.json sed -i '0,/CN/{s/example\.net/'"$PEER_NAME"'/}' config.json sed -i 's/www\.example\.net/'"$PRIVATE_IP"'/' config.json sed -i 's/example\.net/'"$PEER_NAME"'/' config.json cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server config.json | cfssljson -bare server cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer config.json | cfssljson -bare peer 
签出出两对密钥和证书,把每台的机器的 域名 和 IP 替换掉示例的 example 配置,就是这些地方可以改成自己的域名和地址,这个会被用来 check。
可以放到 kubelet 的 static pod 里面,启动 etcd,两个证书分别是用作 server 验证和 client 验证的,server 让别人访问的时候相信你,peer 是你访问别人的时候让别人相信你。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 cat  >/etc/kubernetes/manifests/etcd.yaml  <<EOF apiVersion:  v1 kind:  Pod metadata: labels:     component:  etcd      tier:  control-plane  name:  <podname> namespace:  kube-system spec: containers: -  command:     -  etcd  --name  ${PEER_NAME}  \      -  --data-dir  /var/lib/etcd  \      -  --listen-client-urls  https://${PRIVATE_IP}:2379  \      -  --advertise-client-urls  https://${PRIVATE_IP}:2379  \      -  --listen-peer-urls  https://${PRIVATE_IP}:2380  \      -  --initial-advertise-peer-urls  https://${PRIVATE_IP}:2380  \      -  --cert-file=/certs/server.pem  \      -  --key-file=/certs/server-key.pem  \      -  --client-cert-auth  \      -  --trusted-ca-file=/certs/ca.pem  \      -  --peer-cert-file=/certs/peer.pem  \      -  --peer-key-file=/certs/peer-key.pem  \      -  --peer-client-cert-auth  \      -  --peer-trusted-ca-file=/certs/ca.pem  \      -  --initial-cluster  etcd0=https://<etcd0-ip-address>:2380,etcd1=https://<etcd1-ip-address>:2380,etcd2=https://<etcd2-ip-address>:2380  \      -  --initial-cluster-token  my-etcd-token  \      -  --initial-cluster-state  new      image:  k8s.gcr.io/etcd-amd64:3.1.10      livenessProbe:      httpGet:          path:  /health          port:  2379          scheme:  HTTP      initialDelaySeconds:  15      timeoutSeconds:  15      name:  etcd      env:      -  name:  PUBLIC_IP      valueFrom:          fieldRef:          fieldPath:  status.hostIP      -  name:  PRIVATE_IP      valueFrom:          fieldRef:          fieldPath:  status.podIP      -  name:  PEER_NAME      valueFrom:          fieldRef:          fieldPath:  metadata.name      volumeMounts:      -  mountPath:  /var/lib/etcd      name:  etcd      -  mountPath:  /certs      name:  certs  hostNetwork:  true volumes: -  hostPath:     path:  /var/lib/etcd      type:  DirectoryOrCreate      name:  etcd  -  hostPath:     path:  /etc/kubernetes/pki/etcd      name:  certs  EOF 
然后每个节点的 master init 的配置文件按照下面这个配置,client.pem 是用来让 etcd 相信 apiserver 的。private-ip 和 load-balancer-ip 都是要写到证书的  SAN 里面的,不然用这些 ip 是访问不了 apiserver 的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 apiVersion:  kubeadm.k8s.io/v1alpha1 kind:  MasterConfiguration api:   advertiseAddress:  <private-ip>  etcd:   endpoints:    -  https://<etcd0-ip-address>:2379    -  https://<etcd1-ip-address>:2379    -  https://<etcd2-ip-address>:2379    caFile:  /etc/kubernetes/pki/etcd/ca.pem    certFile:  /etc/kubernetes/pki/etcd/client.pem    keyFile:  /etc/kubernetes/pki/etcd/client-key.pem  networking:   podSubnet:  <podCIDR>  apiServerCertSANs: -  <load-balancer-ip> apiServerExtraArgs:   apiserver-count:  "3"  EOF 
至于怎么做 loadbalance 可以用七层的也可以用四层的,七层把证书配到负载均衡服务上,四层的就不用,自己在裸机器上做可以用 vip + nginx 做一个四层的,也可以把证书放到 nginx 上做七层的,但是在云环境下,都不怎么支持自己配置 vip,需要用云厂商的 lb 服务,这个就看具体提供商的服务怎么配了。