本文我们将了解如何将一个新的 DNS 名称或者 IP 地址添加到 Kubernetes APIServer 使用的 TLS 证书中。在某些情况下默认的证书包含的名称可能不能满足我们的要求,又或者是 APIServer 地址有所变化,都需要重新更新证书。

Kubernetes APIServer 使用数字证书来加密 APIServer 的相关流量以及验证到 APIServer 的连接。所以如果我们想使用命令行客户端(比如 kubectl)连接到 APIServer,并且使用的主机名或者 IP 地址不包括在证书的 subject 的备选名称(SAN)列表中的话,访问的时候可能会出错,会提示对指定的 IP 地址或者主机名访问证书无效。要解决这个问题就需要更新证书,使 SAN 列表中包含所有你将用来访问 APIServer 的 IP 地址或者主机名。

更新 APIServer 证书

因为集群是使用 kubeadm 搭建的,所以我们可以直接使用 kubeadm 来更新 APIServer 的证书,来保证在 SAN 列表中包含额外的名称。

首页我们一个 kubeadm 的配置文件,如果一开始安装集群的时候你就是使用的配置文件,那么我们可以直接更新这个配置文件,但是如果你没有使用配置文件,直接使用的 kubeadm init 来安装的集群,那么我们可以从集群中获取 kubeadm 的配置信息来创建一个配置文件,因为 kubeadm 会将其配置写入到 kube-system 命名空间下面一个名为 kubeadm-config 的 ConfigMap 中。可以直接执行如下所示的命令将该配置导出:

$ kubectl -n kube-system get configmap kubeadm-config -o jsonpath='{.data.ClusterConfiguration}' > kubeadm-init.yaml

kubeadmin-init.yaml 内容如下(configmap里不回包含bootstrapTokens 的内容,从API SERVER 开始)

apiVersion: kubeadm.k8s.io/v1beta2
bootstrapTokens:
- groups:
  - system:bootstrappers:kubeadm:default-node-token
  token: abcdef.0123456789abcdef
  ttl: 24h0m0s
  usages:
  - signing
  - authentication
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 172.25.174.51
  bindPort: 6443
nodeRegistration:
  criSocket: /var/run/dockershim.sock
  name: pass-test03
  taints:
  - effect: NoSchedule
    key: node-role.kubernetes.io/master
---
apiServer:
  extraArgs:
    audit-log-format: json
    audit-log-maxage: "20"
    audit-log-maxbackup: "10"
    audit-log-maxsize: "100"
    audit-log-path: /var/log/kube-audit/kube-audit.log
    audit-policy-file: /etc/kubernetes/audit-policy.yaml
  extraVolumes:
  - hostPath: /etc/kubernetes/audit-policy.yaml
    mountPath: /etc/kubernetes/audit-policy.yaml
    name: audit-config
    pathType: File
    readOnly: true
  - hostPath: /var/log/kube-audit
    mountPath: /var/log/kube-audit
    name: audit-log
    pathType: DirectoryOrCreate
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta2
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: 127.0.0.1:9443
controllerManager:
  extraArgs:
    experimental-cluster-signing-duration: 876000h
dns:
  type: CoreDNS
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: v1.20.1
networking:
  dnsDomain: cluster.local
  podSubnet: 10.244.0.0/16
  serviceSubnet: 10.96.0.0/12
scheduler: {}

上面的配置中并没有列出额外的 SAN 信息,我们要添加一个新的数据,需要在 apiServer 属性下面添加一个 certsSANs 的列表。如果你在启动集群的使用就使用的了 kubeadm 的配置文件,可能里面就已经包含 certSANs 列表了,如果没有我们就需要添加它,比如我们这里要添加一个新的域名 newmaster.local 以及47.108.52.140这个新的 IP 地址,那么我们需要在 apiServer 下面添加如下所示的数据:

apiServer:
  certSANs:
  - newmaster.local
  - master1
  - master2
  - 47.108.52.140

更新完 kubeadm 配置文件后我们就可以更新证书了,首先我们移动现有的 APIServer 的证书和密钥,因为 kubeadm 检测到他们已经存在于指定的位置,它就不会创建新的了。

$ mv /etc/kubernetes/pki/apiserver.{crt,key} ~

 然后直接使用 kubeadm 命令生成一个新的证书:

[root@pass-test03 installation]# kubeadm init phase certs apiserver --config kubeadm-init.yaml 
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local pass-test03] and IPs [10.96.0.1 172.25.174.51 127.0.0.1 47.108.52.140]

通过上面的命令可以查看到 APIServer 签名的 DNS 和 IP 地址信息,一定要和自己的目标签名信息进行对比,如果缺失了数据就需要在上面的 certSANs 中补齐,重新生成证书。

该命令会使用上面指定的 kubeadm 配置文件为 APIServer 生成一个新的证书和密钥,由于指定的配置文件中包含了 certSANs 列表,那么 kubeadm 会在创建新证书的时候自动添加这些 SANs。

最后一步是重启 APIServer 来接收新的证书,最简单的方法是直接杀死 APIServer 的容器(或者直接重启k8s集群):

$ docker ps | grep kube-apiserver | grep -v pause
7fe227a5dd3c        aa63290ccd50                               "kube-apiserver --ad…"   14 hours ago        Up 14 hours                             k8s_kube-apiserver_kube-apiserver-ydzs-master_kube-system_6aa38ee2d66b7d9b6660a88700d00581_0
$ docker kill 7fe227a5dd3c
7fe227a5dd3c

容器被杀掉后,kubelet 会自动重启容器,然后容器将接收新的证书,一旦 APIServer 重启后,我们就可以使用新添加的 IP 地址或者主机名来连接它了,比如我们新添加的 47.108.52.140。

验证
要验证证书是否更新我们可以直接去编辑 kubeconfig 文件中的 APIServer 地址,将其更换为新添加的 IP 地址或者主机名,然后去使用 kubectl 操作集群,查看是否可以正常工作。

当然我们可以使用 openssl 命令去查看生成的证书信息是否包含我们新添加的 SAN 列表数据:

$ openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text
Certificate:
      ......
        Subject: CN=kube-apiserver
        ......
            X509v3 Subject Alternative Name:
                DNS:ydzs-master, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:api.k8s.local, DNS:ydzs-master2, DNS:ydzs-master3, IP Address:10.96.0.1, IP Address:123.59.188.12, IP Address:10.151.30.11, IP Address:10.151.30.70, IP Address:10.151.30.71
......

更新集群配置
如果上面的操作都一切顺利,最后一步是将上面的集群配置信息保存到集群的 kubeadm-config 这个 ConfigMap 中去,这一点非常重要,这样以后当我们使用 kubeadm 来操作集群的时候,相关的数据不会丢失,比如升级的时候还是会带上 certSANs 中的数据进行签名的。

kubeadm init phase upload-config kubeadm --config kubeadm-init.yaml

检查更新后的configmap

$ kubectl -n kube-system get configmap kubeadm-config -o yaml

kubeadm reference