在Kubernetes的部署中, 创建证书, 配置证书是一道绕不过去坎儿, 好在有kubeadm这样的自动化工具, 帮我们去生成, 配置这些证书. 对于只是想体验Kubernetes或只是想测试的亲来说, 这已经够了, 但是作为Kubernetes的集群维护者来说, kubeadm更像是一个黑盒, 本篇文章就来说说黑盒中关于证书的事儿~
使用kubeadm创建完Kubernetes集群后, 默认会在/etc/kubernetes/pki目录下存放集群中需要用到的证书文件, 整体结构如下图所示:

| root@k8s-master:/etc/kubernetes/pki# tree
.
|— apiserver.crt
|— apiserver-etcd-client.crt
|— apiserver-etcd-client.key
|— apiserver.key
|— apiserver-kubelet-client.crt
|— apiserver-kubelet-client.key
|— ca.crt
|— ca.key
|— etcd
| |— ca.crt
| |— ca.key
| |— healthcheck-client.crt
| |— healthcheck-client.key
| |— peer.crt
| |— peer.key
| |— server.crt
| -- server.key<br />&#124;-- front-proxy-ca.crt<br />&#124;-- front-proxy-ca.key<br />&#124;-- front-proxy-client.crt<br />&#124;-- front-proxy-client.key<br />&#124;-- sa.key<br />— sa.pub

1 directory, 22 files

| | —- |

以上22个文件就是kubeadm为我们创建的所有证书相关的文件, 下面我们来一一解析

证书分组

Kubernetes把证书放在了两个文件夹中

  • /etc/kubernetes/pki
  • /etc/kubernetes/pki/etcd

我们再将这22个文件按照更细的粒度去分组

Kubernetes 集群根证书

Kubernetes 集群根证书CA(Kubernetes集群组件的证书签发机构)

  • /etc/kubernetes/pki/ca.crt
  • /etc/kubernetes/pki/ca.key

以上这组证书为签发其他Kubernetes组件证书使用的根证书, 可以认为是Kubernetes集群中证书签发机构之一
由此根证书签发的证书有:

  1. kube-apiserver 组件持有的服务端证书
    • /etc/kubernetes/pki/apiserver.crt
    • /etc/kubernetes/pki/apiserver.key
  2. kubelet 组件持有的客户端证书, 用作 kube-apiserver 主动向 kubelet 发起请求时的客户端认证
    • /etc/kubernetes/pki/apiserver-kubelet-client.crt
    • /etc/kubernetes/pki/apiserver-kubelet-client.key

注意: Kubernetes集群组件之间的交互是双向的, kubelet 既需要主动访问 kube-apiserver, kube-apiserver 也需要主动向 kubelet 发起请求, 所以双方都需要有自己的根证书以及使用该根证书签发的服务端证书和客户端证书. 在 kube-apiserver 中, 一般明确指定用于 https 访问的服务端证书和带有CN 用户名信息的客户端证书. 而在 kubelet 的启动配置中, 一般只指定了 ca 根证书, 而没有明确指定用于 https 访问的服务端证书, 这是因为, 在生成服务端证书时, 一般会指定服务端地址或主机名, kube-apiserver 相对变化不是很频繁, 所以在创建集群之初就可以预先分配好用作 kube-apiserver 的 IP 或主机名/域名, 但是由于部署在 node 节点上的 kubelet 会因为集群规模的变化而频繁变化, 而无法预知 node 的所有 IP 信息, 所以 kubelet 上一般不会明确指定服务端证书, 而是只指定 ca 根证书, 让 kubelet 根据本地主机信息自动生成服务端证书并保存到配置的cert-dir文件夹中.
好了, 至此, Kubernetes集群根证书所签发的证书都在上面了, 算上根证书一共涉及到6个文件, 22-6=16, 我们还剩下16个文件

汇聚层证书

kube-apiserver 的另一种访问方式就是使用 kubectl proxy 来代理访问, 而该证书就是用来支持SSL代理访问的. 在该种访问模式下, 我们是以http的方式发起请求到代理服务的, 此时, 代理服务会将该请求发送给 kube-apiserver, 在此之前, 代理会将发送给 kube-apiserver 的请求头里加入证书信息, 以下两个配置
API Aggregation允许在不修改Kubernetes核心代码的同时扩展Kubernetes API. 开启 API Aggregation 需要在 kube-apiserver 中添加如下配置:

| —requestheader-client-ca-file=
—requestheader-allowed-names=front-proxy-client
—requestheader-extra-headers-prefix=X-Remote-Extra-
—requestheader-group-headers=X-Remote-Group
—requestheader-username-headers=X-Remote-User
—proxy-client-cert-file=
—proxy-client-key-file=

| | —- |

官方警告: 除非你了解保护 CA 使用的风险和机制, 否则不要在不通上下文中重用已经使用过的 CA
如果 kube-proxy 没有和 API server 运行在同一台主机上,那么需要确保启用了如下 apiserver 标记:
—enable-aggregator-routing=true

| 客户端 —-发起请求—-> 代理 —-Add Header:发起请求—-> kube-apiserver
(客户端证书) (服务端证书)

| | —- |

kube-apiserver 代理根证书(客户端证书)
用在requestheader-client-ca-file配置选项中, kube-apiserver 使用该证书来验证客户端证书是否为自己所签发

  • /etc/kubernetes/pki/front-proxy-ca.crt
  • /etc/kubernetes/pki/front-proxy-ca.key

由此根证书签发的证书只有一组:
代理层(如汇聚层aggregator)使用此套代理证书来向 kube-apiserver 请求认证

  1. 代理端使用的客户端证书, 用作代用户与 kube-apiserver 认证
    • /etc/kubernetes/pki/front-proxy-client.crt
    • /etc/kubernetes/pki/front-proxy-client.key

参考文档:

至此, 刨除代理专用的证书外, 还剩下 16-4=12 个文件

etcd 集群根证书

etcd集群所用到的证书都保存在/etc/kubernetes/pki/etcd这路径下, 很明显, 这一套证书是用来专门给etcd集群服务使用的, 设计以下证书文件
etcd 集群根证书CA(etcd 所用到的所有证书的签发机构)

  • /etc/kubernetes/pki/etcd/ca.crt
  • /etc/kubernetes/pki/etcd/ca.key

由此根证书签发机构签发的证书有:

  1. etcd server 持有的服务端证书
    • /etc/kubernetes/pki/etcd/server.crt
    • /etc/kubernetes/pki/etcd/server.key
  2. peer 集群中节点互相通信使用的客户端证书
    • /etc/kubernetes/pki/etcd/peer.crt
    • /etc/kubernetes/pki/etcd/peer.key注: Peer:对同一个etcd集群中另外一个Member的称呼
  3. pod 中定义 Liveness 探针使用的客户端证书kubeadm 部署的 Kubernetes 集群是以 pod 的方式运行 etcd 服务的, 在该 pod 的定义中, 配置了 Liveness 探活探针

    • /etc/kubernetes/pki/etcd/healthcheck-client.crt
    • /etc/kubernetes/pki/etcd/healthcheck-client.key当你 describe etcd 的 pod 时, 会看到如下一行配置: | Liveness: exec [/bin/sh -ec ETCDCTL_API=3 etcdctl —endpoints=https://[127.0.0.1]:2379 —cacert=/etc/kubernetes/pki/etcd/ca.crt —cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt —key=/etc/kubernetes/pki/etcd/healthcheck-client.key get foo] delay=15s timeout=15s period=10s #success=1 #failure=8

    | | —- |

  4. 配置在 kube-apiserver 中用来与 etcd server 做双向认证的客户端证书

    • /etc/kubernetes/pki/apiserver-etcd-client.crt
    • /etc/kubernetes/pki/apiserver-etcd-client.key

至此, 介绍了涉及到 etcd 服务的10个证书文件, 12-10=2, 仅剩两个没有介绍到的文件啦, 胜利✌️在望, 坚持一下~

Serveice Account秘钥

最后介绍的这组”证书”其实不是证书, 而是一组秘钥. 看着后缀名是不是有点眼熟呢, 没错, 这组秘钥对儿其实跟我们在Linux上创建, 用于免密登录的密钥对儿原理是一样的~
这组的密钥对儿仅提供给 kube-controller-manager 使用. kube-controller-manager 通过 sa.key 对 token 进行签名, master 节点通过公钥 sa.pub 进行签名的验证

  • /etc/kubernetes/pki/sa.key
  • /etc/kubernetes/pki/sa.pub

至此, kubeadm 工具帮我们创建的所有证书文件都已经介绍完了, 整个 Kubernetes&etcd 集群中所涉及到的绝大部分证书都差不多在这里了. 有的行家可能会看出来, 至少还少了一组证书呀, 就是 kube-proxy 持有的证书怎么没有自动生成呀. 因为 kubeadm 创建的集群, kube-proxy 是以 pod 形式运行的, 在 pod 中, 直接使用 service account 与 kube-apiserver 进行认证, 此时就不需要再单独为 kube-proxy 创建证书了. 如果你的 kube-proxy 是以守护进程的方式直接运行在宿主机的, 那么你就需要为它创建一套证书了. 创建的方式也很简单, 直接使用上面第一条提到的 Kubernetes 集群根证书 进行签发就可以了(注意CN和O的设置)

证书查看方法

openssl x509 -noout -text -in ca.crt

查看证书是否是CA证书

CA证书是可以用于校验证书签名,非CA证书是不行的。 https://blog.csdn.net/gx11251143/article/details/113243925
X509v3 Basic Constraints: critical
CA:TRUE

验证证书

openssl verify -CAfile apiserver-ca.crt client-ca.crt

etcd 证书配置

需要在 etcd 的启动命令行中配置以下证书相关参数:

  • etcd 对外提供服务的服务器证书及私钥。
  • etcd 节点之间相互进行认证的 peer 证书、私钥以及验证 peer 的 CA。
  • etcd 验证访问其服务的客户端的 CA。

    1. /usr/local/bin/etcd \\
    2. --cert-file=/etc/etcd/kube-etcd.pem \\ # 对外提供服务的服务器证书
    3. --key-file=/etc/etcd/kube-etcd-key.pem \\ # 服务器证书对应的私钥
    4. --peer-cert-file=/etc/etcd/kube-etcd-peer.pem \\ # peer 证书,用于 etcd 节点之间的相互访问
    5. --peer-key-file=/etc/etcd/kube-etcd-peer-key.pem \\ # peer 证书对应的私钥
    6. --trusted-ca-file=/etc/etcd/cluster-root-ca.pem \\ # 用于验证访问 etcd 服务器的客户端证书的 CA 根证书
    7. --peer-trusted-ca-file=/etc/etcd/cluster-root-ca.pem\\ # 用于验证 peer 证书的 CA 根证书
    8. ...

    kube-apiserver 证书配置

    需要在 kube-apiserver 中配置以下证书相关参数:

  • kube-apiserver 对外提供服务的服务器证书及私钥。

  • kube-apiserver 访问 etcd 所需的客户端证书及私钥。
  • kube-apiserver 访问 kubelet 所需的客户端证书及私钥。
  • 验证访问其服务的客户端的 CA。
  • 验证 etcd 服务器证书的 CA 根证书。
  • 验证 service account token 的公钥。
    1. /usr/local/bin/kube-apiserver \\
    2. --tls-cert-file=/var/lib/kubernetes/kube-apiserver.pem \\ # 用于对外提供服务的服务器证书
    3. --tls-private-key-file=/var/lib/kubernetes/kube-apiserver-key.pem \\ # 服务器证书对应的私钥
    4. --etcd-certfile=/var/lib/kubernetes/kube-apiserver-etcd-client.pem \\ # 用于访问 etcd 的客户端证书
    5. --etcd-keyfile=/var/lib/kubernetes/kube-apiserver-etcd-client-key.pem \\ # 用于访问 etcd 的客户端证书的私钥
    6. --kubelet-client-certificate=/var/lib/kubernetes/kube-apiserver-kubelet-client.pem \\ # 用于访问 kubelet 的客户端证书
    7. --kubelet-client-key=/var/lib/kubernetes/kube-apiserver-kubelet-client-key.pem \\ # 用于访问 kubelet 的客户端证书的私钥
    8. --client-ca-file=/var/lib/kubernetes/cluster-root-ca.pem \\ # 用于验证 访问 kube-apiserver 的客户端的证书的 CA 根证书
    9. --etcd-cafile=/var/lib/kubernetes/cluster-root-ca.pem \\ # 用于验证 etcd 服务器证书的 CA 根证书
    10. --kubelet-certificate-authority=/var/lib/kubernetes/cluster-root-ca.pem \\ # 用于验证 kubelet 服务器证书的 CA 根证书
    11. --service-account-key-file=/var/lib/kubernetes/service-account.pem \\ # 用于验证 service account token 的公钥
    12. ...

    采用 kubeconfig 访问 kube-apiserver

  1. apiVersion: v1
  2. clusters:
  3. - cluster:
  4. # 用于验证 kube-apiserver 服务器证书的 CA 根证书
  5. certificate-authority-data: xxx
  6. server: https://localhost:8443
  7. name: kubernetes
  8. contexts:
  9. - context:
  10. cluster: kubernetes
  11. user: system:kube-controller-manager
  12. name: system:kube-controller-manager@kubernetes
  13. current-context: system:kube-controller-manager@kubernetes
  14. kind: Config
  15. preferences: {}
  16. users:
  17. - name: system:kube-controller-manager
  18. user:
  19. # 用于访问 kube-apiserver 的客户端证书
  20. client-certificate-data: xxx
  21. # 客户端证书对应的私钥
  22. client-key-data: xxx

Service Account 证书

Kubernetes 中有两类用户,一类为 user account,一类为 service account。 service account 主要被 pod 用于访问 kube-apiserver。 在为一个 pod 指定了 service account 后,kubernetes 会为该 service account 生成一个 JWT token,并使用 secret 将该 service account token 加载到 pod 上。pod 中的应用可以使用 service account token 来访问 api server。service account 证书被用于生成和验证 service account token。该证书的用法和前面介绍的其他证书不同,因为实际上使用的是其公钥和私钥,而并不需要对证书进行验证。
image.png

kubelet ServerTLSBootstrap

会自动生成kubelet作为server的https证书
�参数名为 rotate-server-certificates 或者 /var/lib/kubelet/config.yaml 中的 serverTLSBootstrap
�相关参数 —tls-cert-file 和 —tls-private-key-file,表明证书保存的地方
相关代码

  1. if kubeCfg.ServerTLSBootstrap && kubeDeps.TLSOptions != nil && utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate) {
  2. klet.serverCertificateManager, err = kubeletcertificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg, klet.nodeName, klet.getLastObservedNodeAddresses, certDirectory)
  3. if err != nil {
  4. return nil, fmt.Errorf("failed to initialize certificate manager: %v", err)
  5. }
  6. kubeDeps.TLSOptions.Config.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
  7. cert := klet.serverCertificateManager.Current()
  8. if cert == nil {
  9. return nil, fmt.Errorf("no serving certificate available for the kubelet")
  10. }
  11. return cert, nil
  12. }
  13. }

kubelet 默认生成Server证书

- The kube-apiserver --kubelet-https _flag is deprecated. kube-apiserver connections to kubelets now unconditionally use https (kubelets have unconditionally used https to serve the endpoints the apiserver communicates with since before v1.0). ([#91630](_https://github.com/kubernetes/kubernetes/pull/91630), @liggitt) **[SIG API Machinery and Node]

**

没有指定ServerTLSBootstrap、TLSCertFile、TLSPrivateKeyFile 的时候,会默认生成kubelet.crt证书

  1. func InitializeTLS(kf *options.KubeletFlags, kc *kubeletconfiginternal.KubeletConfiguration) (*server.TLSOptions, error) {
  2. if !kc.ServerTLSBootstrap && kc.TLSCertFile == "" && kc.TLSPrivateKeyFile == "" {
  3. kc.TLSCertFile = path.Join(kf.CertDirectory, "kubelet.crt")
  4. kc.TLSPrivateKeyFile = path.Join(kf.CertDirectory, "kubelet.key")
  5. canReadCertAndKey, err := certutil.CanReadCertAndKey(kc.TLSCertFile, kc.TLSPrivateKeyFile)
  6. if err != nil {
  7. return nil, err
  8. }
  9. if !canReadCertAndKey {
  10. hostName, err := nodeutil.GetHostname(kf.HostnameOverride)
  11. if err != nil {
  12. return nil, err
  13. }
  14. cert, key, err := certutil.GenerateSelfSignedCertKey(hostName, nil, nil)
  15. if err != nil {
  16. return nil, fmt.Errorf("unable to generate self signed cert: %w", err)
  17. }
  18. if err := certutil.WriteCert(kc.TLSCertFile, cert); err != nil {
  19. return nil, err
  20. }
  21. if err := keyutil.WriteKey(kc.TLSPrivateKeyFile, key); err != nil {
  22. return nil, err
  23. }
  24. klog.V(4).InfoS("Using self-signed cert", "TLSCertFile", kc.TLSCertFile, "TLSPrivateKeyFile", kc.TLSPrivateKeyFile)
  25. }
  26. }
  27. tlsCipherSuites, err := cliflag.TLSCipherSuites(kc.TLSCipherSuites)
  28. if err != nil {
  29. return nil, err
  30. }
  31. if len(tlsCipherSuites) > 0 {
  32. insecureCiphers := cliflag.InsecureTLSCiphers()
  33. for i := 0; i < len(tlsCipherSuites); i++ {
  34. for cipherName, cipherID := range insecureCiphers {
  35. if tlsCipherSuites[i] == cipherID {
  36. klog.InfoS("Use of insecure cipher detected.", "cipher", cipherName)
  37. }
  38. }
  39. }
  40. }
  41. minTLSVersion, err := cliflag.TLSVersion(kc.TLSMinVersion)
  42. if err != nil {
  43. return nil, err
  44. }
  45. tlsOptions := &server.TLSOptions{
  46. Config: &tls.Config{
  47. MinVersion: minTLSVersion,
  48. CipherSuites: tlsCipherSuites,
  49. },
  50. CertFile: kc.TLSCertFile,
  51. KeyFile: kc.TLSPrivateKeyFile,
  52. }
  53. if len(kc.Authentication.X509.ClientCAFile) > 0 {
  54. clientCAs, err := certutil.NewPool(kc.Authentication.X509.ClientCAFile)
  55. if err != nil {
  56. return nil, fmt.Errorf("unable to load client CA file %s: %w", kc.Authentication.X509.ClientCAFile, err)
  57. }
  58. // Specify allowed CAs for client certificates
  59. tlsOptions.Config.ClientCAs = clientCAs
  60. // Populate PeerCertificates in requests, but don't reject connections without verified certificates
  61. tlsOptions.Config.ClientAuth = tls.RequestClientCert
  62. }
  63. return tlsOptions, nil
  64. }

使用 TLS bootstrapping 简化 Kubelet 证书制作

在安装 Kubernetes 时,我们需要为每一个工作节点上的 Kubelet 分别生成一个证书。由于工作节点可能很多,手动生成 Kubelet 证书的过程会比较繁琐。为了解决这个问题,Kubernetes 提供了 TLS bootstrapping的方式来简化 Kubelet 证书的生成过程。其原理是预先提供一个 bootstrapping token,kubelet 采用该 bootstrapping token 进行客户端验证,调用 kube-apiserver 的证书签发 API 来生成 自己需要的证书。要启用该功能,需要在 kube-apiserver 中启用 —enable-bootstrap-token-auth ,并创建一个 kubelet 访问 kube-apiserver 使用的 bootstrap token secret。如果使用 kubeadmin 安装,可以使用 kubeadm token create命令来创建 token。
采用TLS bootstrapping 生成证书的流程如下:

  1. 调用 kube-apiserver 生成一个 bootstrap token。
  2. 将该 bootstrap token 写入到一个 kubeconfig 文件中,作为 kubelet 调用 kube-apiserver 的客户端验证方式。
  3. 通过 —bootstrap-kubeconfig 启动参数将 bootstrap token 传递给 kubelet 进程。
  4. Kubelet 采用bootstrap token 调用 kube-apiserver API,生成自己所需的服务器和客户端证书。
  5. 证书生成后,Kubelet 采用生成的证书和 kube-apiserver 进行通信,并删除本地的 kubeconfig 文件,以避免 bootstrap token 泄漏风险。

相关代码

  1. if len(s.BootstrapKubeconfig) > 0 {
  2. if err := bootstrap.LoadClientCert(ctx, s.KubeConfig, s.BootstrapKubeconfig, s.CertDirectory, nodeName); err != nil {
  3. return nil, nil, err
  4. }
  5. }

kubeadm join使用cluster-info

  • kubeadm以insecure方式访问cluster-info
  • 使用token验证cluster-info中的jws签名,验证通过后,通过该cluster-info中的 kubeconfig,可以获取 CA 相关信息
  • 使用ca-sert-hash验证cluster-info中的ca
  • 获取ca并重新建立安全连接,并再次验证与cluster-info中ca的一致性

cluster-info中,kubeconfig 项即保存的 kubeconfig 内容,jws-kubeconfig- 项保存的使用该 token 对该 kubeconfig 的签名结果。

cluster-info configmap的案例:

  1. data:
  2. jws-kubeconfig-51xbhe: eyJhbGciOiJIUzI1NiIsImtpZCI6IjUxeGJoZSJ9..uclYAQAK_Zphm0QhAV4im3KCmMNex7WXxLkbB4K-adk
  3. kubeconfig: |
  4. apiVersion: v1
  5. clusters:
  6. - cluster:
  7. certificate-authority-data: xxx=
  8. server: https://10.0.0.9:6443
  9. name: ""
  10. contexts: null
  11. current-context: ""
  12. kind: Config
  13. preferences: {}
  14. users: null


kubeadm join 的验签过程即使用用户提供的 token、kube-public/cluster-info 中保存的 kubeconfig 信息进行 jws 签名,将签名结果与 jws-kubeconfig- 项的内容对比,相同则验证通过,认为该 APIServer 可以被信任。

经过以上步骤之后,才开始kubelet TLS bootstrapping。同时 kubeadm join 提供的 token,也是bootstrap token。
kubeadm join的流程:
image.png