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 、 HPA 、VPA 等客户端向 API Server 发出指标数据查询请求时,由 API Server 调用 Metrics Server 获取数据后再返回给客户端最终结果。

从上面的这份时序图中,我们可以看到:
Metrics Server 既作为服务端与 API Server 通信,也作为客户端与 Kubelet 通信。
通常来说,为了更好地实现通信安全,需校验通信双方的数字证书是否均由可信 CA 签名。
kubelet-insecure-tls: Metrics Server 不校验 Kubelet 服务端证书的 CA。
insecureSkipTLSVerify:true :API Server 不校验 Metrics Server 服务端证书的 CA。
无疑,这都是不那么安全的做法。
3. 实现与 Kubelet 安全通信
这一节的目标是实现 Metrics Server 与 Kubelet 的安全通信,也就是消除 kubelet-insecure-tls 配置。
3.0. 问题说明
cluster-root-ca:kubeadm 创建集群时,默认创建私钥和自签名 CA:/etc/kubernetes/pki/ca.crt。
kubelet.crt:kubelet 首次启动时,默认创建私钥和自签名证书:/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 Server 将 cluster-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>
首先,手动批准 kubelet 的 CSR;
其后,signing controller 会自动调用名为 kubernetes.io/kubelet-serving 的 signer 使用 cluster-root-ca 进行签名。
最后,kubelet 会自动取回已签名证书,并将其用作服务端证书。
证书签名详细介绍见:Kubernetes: Certificates and Certificate Signing Requests
3.2. 修改 Metrics Server 配置
上一小节的操作,是让 kubelet 使用 cluster-root-ca 签名的服务端证书。
这一小节的操作,是让 Metrics Server 将 cluster-root-ca 列为可信 CA。
3.2.1. kube-root-ca.crt
kube-controller-manager 会自动在所有命名空间创建名为 kube-root-ca.crt 的 ConfigMap。
# 执行命令,查看该 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.crt 的 ConfigMap,然后将其内容指定为用于校验 kubelet 服务端证书的 CA。
打开 Metrics Server 的 components.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 Server 与 Metrics 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"
解决方案:
Metrics Server 使用由
cluster-root-ca签名的服务端证书。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
注:
CN和SAN均为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 Server 的 components.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 Server 的 components.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