Metrics Server 并非 Kubernetes 核心 API,而是以聚合层方式提供的扩展 API 服务。

因此,为了使其与 Kubernetes 核心 API 之间实现通信安全,需要额外进行一些稍显复杂的配置。

1. 快速安装

# 获取资源配置
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

如为测试环境,仅需添加一行配置:--kubelet-insecure-tls,然后 kubectl apply -f components.yaml 即可完成安装。

# 省略 ……
apiVersion: apps/v1
kind: Deployment
# 省略 ……
spec:
  # 省略 ……
  template:
    spec:
      containers:
      - args:
        - --cert-dir=/tmp
        - --secure-port=10250
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-use-node-status-port
        - --metric-resolution=15s
        # 1.仅需添加下面这行:不验证 Kubelet 的服务证书的 CA(仅用于测试环境)
        - --kubelet-insecure-tls
# 省略 ……
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
# 省略 ……
spec:
  group: metrics.k8s.io
  groupPriorityMinimum: 100
  # 2. 跳过 TLS 校验
  insecureSkipTLSVerify: true
# 省略 ……

示例配置如上,这里我们需特别关注两项配置:

1、kubelet-insecure-tls

2、insecureSkipTLSVerify:true

2. 交互说明

为了更好地说明问题,我们先看看指标采集的交互过程。

Kubelet 间隔采集 Pod(Container) 的原始指标数据。

Metrics Server 间隔调用 Kubelet,抓取汇总后的节点资源指标数据并缓存。

③ 当 kubectl top HPAVPA 等客户端向 API Server 发出指标数据查询请求时,由 API Server 调用 Metrics Server 获取数据后再返回给客户端最终结果。

metrics-sequence

从上面的这份时序图中,我们可以看到:

Metrics Server 既作为服务端与 API Server 通信,也作为客户端与 Kubelet 通信。

通常来说,为了更好地实现通信安全,需校验通信双方的数字证书是否均由可信 CA 签名。

kubelet-insecure-tlsMetrics Server 不校验 Kubelet 服务端证书的 CA。

insecureSkipTLSVerify:true API Server 不校验 Metrics Server 服务端证书的 CA。

无疑,这都是不那么安全的做法。

3. 实现与 Kubelet 安全通信

这一节的目标是实现 Metrics ServerKubelet 的安全通信,也就是消除 kubelet-insecure-tls 配置。

3.0. 问题说明

cluster-root-cakubeadm 创建集群时,默认创建私钥和自签名 CA:/etc/kubernetes/pki/ca.crt

kubelet.crtkubelet 首次启动时,默认创建私钥和自签名证书:/var/lib/kubelet/pki/kubelet.crt

情形一

kubeadm 初始化集群时,如果未配置 serverTLSBootstrap: true,那么 kubelet 将会使用自签名的 kubelet.crt 作为服务端证书。

情形二

kubeadm 初始化集群时,如果有配置 serverTLSBootstrap: true,那么 kubelet 会自动发送证书签名请求并使用由 cluster-root-ca 签名的服务端证书。

情形说明

为了消除 kubelet-insecure-tls 配置,需进行如下操作:

Step1: kubelet 使用 cluster-root-ca 签名的服务端证书;

Step2: Metrics Servercluster-root-ca 列为可信 CA。

对于情形一,需执行 Step1 和 Step2;

对于情形二,则只需执行 Step2 ,因为 kubelet 使用的已经是由 cluster-root-ca 签名的服务端证书。

3.1. kubelet 证书签发

注:此小节仅当情形一才需执行。

3.1.1. 更新 kubelet 全局配置

主节点运行如下命令,编辑 kubelet-config

# 编辑 ConfigMap: kubelet-config
kubectl edit cm kubelet-config -n kube-system

添加一行 serverTLSBootstrap: true,内容类似如下:

apiVersion: v1
kind: ConfigMap
data:
  kubelet: |
    apiVersion: kubelet.config.k8s.io/v1beta1
    kind: KubeletConfiguration
    # 证书轮换:证书即将过期时自动申请新证书
    rotateCertificates: true
    # 添加这一行:启用 serverTLSBootstrap
    serverTLSBootstrap: true
    # 省略 ……
# 省略 ……

3.1.2. 更新 kubelet 节点配置

除了在主节点更新 ConfigMap,还需在每个节点(主节点和工作节点)更新 kubelet 的配置文件。

sudo vim /var/lib/kubelet/config.yaml

同样是添加一行 serverTLSBootstrap: true,内容类似如下:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
# 添加这一行:启用 serverTLSBootstrap
serverTLSBootstrap: true
# 证书轮换:证书即将过期时自动申请新证书
rotateCertificates: true
# 省略 ……

3.1.3. 重启 kubelet

每个节点(主节点和工作节点)均需重启 kubelet 服务,服务重启后会自动发送证书签名请求(CSR)。

sudo systemctl restart kubelet

3.1.4. 批准 CSR

回到主节点,继续操作:

# 获取证书签名请求
kubectl get csr

# 批准 CSR
kubectl certificate approve <csr-name>

首先,手动批准 kubeletCSR

其后,signing controller 会自动调用名为 kubernetes.io/kubelet-servingsigner 使用 cluster-root-ca 进行签名。

最后,kubelet 会自动取回已签名证书,并将其用作服务端证书。

证书签名详细介绍见:Kubernetes: Certificates and Certificate Signing Requests

3.2. 修改 Metrics Server 配置

上一小节的操作,是让 kubelet 使用 cluster-root-ca 签名的服务端证书。

这一小节的操作,是让 Metrics Servercluster-root-ca 列为可信 CA。

3.2.1. kube-root-ca.crt

kube-controller-manager 会自动在所有命名空间创建名为 kube-root-ca.crtConfigMap

# 执行命令,查看该 ConfigMap
kubectl -n kube-system get cm kube-root-ca.crt -o yaml

可以看到,其中已包含 cluster-root-ca 的证书内容,类似如下:

apiVersion: v1
data:
  ca.crt: |
    -----BEGIN CERTIFICATE-----
    MIIDBTCCAe2gAwIBAgIIRYt4xi7A41EwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
    # 省略 ……
    -----END CERTIFICATE-----
kind: ConfigMap
# 省略 ……

3.2.2. 修改 Metrics Server 配置

首先让 Metrics Server 挂载这个名为 kube-root-ca.crtConfigMap,然后将其内容指定为用于校验 kubelet 服务端证书的 CA。

打开 Metrics Servercomponents.yaml 文件,修改 kind: Deployment

apiVersion: apps/v1
kind: Deployment
# 省略 ……
spec:
  # 省略 ……
  template:
    # 省略 ……
    spec:
      containers:
        - args:
            - --cert-dir=/tmp
            - --secure-port=4443
            - --kubelet-preferred-address-types=InternalIP,Hostname,ExternalIP
            # 3、指定用于校验 kubelet 服务端证书的 CA
            - --kubelet-certificate-authority=/etc/certs/ca.crt
            # - --kubelet-insecure-tls 这一行删除
            # 省略 ……
          image: k8s.gcr.io/metrics-server/metrics-server:v0.6.1
          name: metrics-server
          volumeMounts:
            - mountPath: /tmp
              name: tmp-dir
            # 2、定义挂载路径
            - mountPath: /etc/certs
              name: ca-certs-volume
              readOnly: true
          # 省略 ……
      volumes:
        - name: tmp-dir
          emptyDir: {}
        # 1、定义卷
        - name: ca-certs-volume
          configMap:
            # 指向存有 ca 证书的 ConfigMap
            name: cluster-root-ca

3.3. 小结

关于消除 kubelet-insecure-tls 的配置已经全部完成。

我们可以重新执行 kubectl apply -f components.yaml 来应用更改。

服务重启后,Metrics Server 将使用 cluster-root-ca 来校验 Kubelet 的服务端证书。

4. 实现与 API Server 安全通信

这一节的目标是实现 API ServerMetrics Server 的安全通信,即配置 insecureSkipTLSVerify false

4.0. 问题说明

为了更好地说明问题,我们先不做其它修改,直接把 insecureSkipTLSVerify 设置为 false,然后查询指标:

kubectl top nodes

此时会报 error: Metrics API not available 异常。

查看 Metrics Server 日志:

kubectl -n kube-system logs -f metrics-server-6b6c7b48b9-gf776

从日志中可以看到 Metrics Server 的服务端证书使用的是自签名证书。

I0926 20:23:16.929724       1 handler.go:288] Adding GroupVersion metrics.k8s.io v1beta1 to ResourceManager
I0926 20:23:17.043323       1 secure_serving.go:211] Serving securely on [::]:10250
I0926 20:23:17.043336       1 dynamic_serving_content.go:135] "Starting controller" name="serving-cert::/tmp/apiserver.crt::/tmp/apiserver.key"

查看 API Server 日志:

kubectl -n kube-system logs kube-apiserver-k8s-control-1

从日志中可以看到 API Server 对该自签名证书的验证失败。

E0926 20:29:06.791628       1 controller.go:102] "Unhandled Error" err=<
	loading OpenAPI spec for "v1beta1.metrics.k8s.io" failed with: failed to download v1beta1.metrics.k8s.io: failed to retrieve openAPI spec, http error: ResponseCode: 503, Body: error trying to reach service: tls: failed to verify certificate: x509: certificate is valid for localhost, localhost, not metrics-server.kube-system.svc
	, Header: map[Content-Type:[text/plain; charset=utf-8] X-Content-Type-Options:[nosniff]]
 > logger="UnhandledError"

解决方案

  1. Metrics Server 使用由 cluster-root-ca 签名的服务端证书。

  2. API Server 使用 cluster-root-ca 校验 Metrics Server 的服务端证书。

4.1. Metrics Server 证书签发

为了简单明了,我在这里将使用 openssl 来处理 生成私钥、创建 CSR 、签发证书 等工作。

生产环境:推荐使用 cert-manager,可自动处理证书轮转等,详见:https://cert-manager.io/

生成私钥

openssl genrsa -out metrics-server.key 2048

创建配置

vim metrics-server-csr.conf

配置内容:

[req]
distinguished_name = dn
req_extensions = v3_ext
prompt = no

[dn]
C = CN
CN = metrics-server.kube-system.svc

[v3_ext]
subjectAltName = @alt_names
extendedKeyUsage = serverAuth

[alt_names]
DNS.1 = metrics-server.kube-system.svc

注:

CNSAN 均为 metrics-server.kube-system.svc

DNS 必须包含完整域名。

创建 CSR

openssl req -new -key metrics-server.key -out metrics-server.csr -config metrics-server-csr.conf

签发证书

sudo openssl x509 -req -in metrics-server.csr \
-CA /etc/kubernetes/pki/ca.crt \
-CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial \
-out metrics-server.crt \
-days 365 \
-sha256 \
-extfile metrics-server-csr.conf \
-extensions v3_ext

创建 Secret

此 Secret 用于保存私钥和证书。

kubectl create secret tls metrics-server-tls \
  --cert=./metrics-server.crt \
  --key=./metrics-server.key \
  --namespace=kube-system

4.2. 修改 Metrics Server 配置

4.2.1. Deployment 部分

打开 Metrics Servercomponents.yaml 文件,修改 kind: Deployment

apiVersion: apps/v1
kind: Deployment
# 省略 ......
spec:
  template:
    # 省略 ......
    spec:
      containers:
      - args:
        # 7、删除原来用于指定服务器证书存放位置的相关参数
        # - --cert-dir=/tmp
        - --secure-port=10250
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-use-node-status-port
        - --metric-resolution=15s
        # 指定 kubelet 服务端证书的 CA
        - --kubelet-certificate-authority=/etc/certs/ca.crt
        # 6、指定 metrics-server 服务端证书
        - --tls-cert-file=/etc/metrics-server/tls.crt
        # 5、指定 metrics-server 服务端密钥
        - --tls-private-key-file=/etc/metrics-server/tls.key
        image: registry.k8s.io/metrics-server/metrics-server:v0.8.0
        imagePullPolicy: IfNotPresent
        volumeMounts:
        # 4、删除原来用于挂载自签名证书的 tmp-dir
        #- mountPath: /tmp
        #  name: tmp-dir
        # -----------------------------
        # kubelet 服务端证书 CA 文件目录
        - mountPath: /etc/certs
          name: ca-certs-volume
          readOnly: true
        # 3、metrics-server 服务端证书文件目录
        - mountPath: /etc/metrics-server
          name: metrics-server-tls-volume
          readOnly: true
      volumes:
      # 2、删除原来用于存放自签名证书的 tmp-dir
      #- name: tmp-dir
      #  emptyDir: {}
      # -----------------------------
      # 存放有 kubelet 服务端证书的 CA
      - name: ca-certs-volume
        configMap:
          name: kube-root-ca.crt
      # 1、存放有 metrics-server 服务端证书和私钥
      - name: metrics-server-tls-volume
        secret:
          secretName: metrics-server-tls

这里共进行了 7 处修改。

4.2.2. APIService 部分

创建 CA 证书包

# 把 cluster-root-ca 文件内容转换为单行文本
cat /etc/kubernetes/pki/ca.crt | base64 | tr -d '\n'

打开 Metrics Servercomponents.yaml 文件,修改 kind: APIService

apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  labels:
    k8s-app: metrics-server
  name: v1beta1.metrics.k8s.io
spec:
  group: metrics.k8s.io
  groupPriorityMinimum: 100
  # 1、添加 caBundle,将之前生成的单行的 CA 证书包内容完整粘贴到此
  # 目的:告知 API Server 用此 CA 来校验 Metrics Server 的服务端证书
  caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS # 省略......
  # 2、修改为 false
  insecureSkipTLSVerify: false
  service:
    name: metrics-server
    namespace: kube-system
  version: v1beta1
  versionPriority: 100

这里共进行了 2 处修改。

4.3. 小结

关于将 insecureSkipTLSVerify 修改为 false 的工作已全部完成。

我们可以再次执行 kubectl apply -f components.yaml 来应用更改。

服务重启后,Metrics Server 将使用 cluster-root-ca 签名的服务端证书,而 API Server 将使用 cluster-root-ca 校验 Metrics Server 的服务端证书。

5. 简单测试

为了减小篇幅,这里省去自动扩缩容测试,只简单测试下 kubectl top

# 获取节点指标信息
kubectl top nodes

# 显示信息如下
NAME            CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
k8s-control-1   306m         7%       2469Mi          31%
k8s-worker-1    70m          1%       983Mi           12%
k8s-worker-2    80m          2%       1121Mi          14%

# 获取 kube-system 命名空间下的 pod 指标信息
kubectl -n kube-system top pods

# 显示信息如下
NAME                                    CPU(cores)   MEMORY(bytes)
coredns-66bc5c9577-2xk2t                3m           65Mi
coredns-66bc5c9577-9fsqd                3m           20Mi
etcd-k8s-control-1                      56m          92Mi
kube-apiserver-k8s-control-1            105m         478Mi
kube-controller-manager-k8s-control-1   61m          136Mi
kube-proxy-8cc8p                        1m           58Mi
kube-proxy-bgm5t                        1m           59Mi
kube-proxy-phfzw                        1m           59Mi
kube-scheduler-k8s-control-1            14m          65Mi
metrics-server-699d57f65-rvwpp          7m           75Mi

顺利获取指标信息,部署成功!

6. 结语

关于 Metrics Server 的通信安全,其实还有一些点可以聊。

但罗列过多未免失去重点,所以,这篇文章就先到此结束吧。

7. 参考文档

Kubernetes: Resource metrics pipeline

Kubernetes: Configure the Aggregation Layer

Kubernetes: Securing a Cluster

Kubelet authentication/authorization

Metrics Server: command-line-flags