主题
二进制集群装 metrics-server:聚合 API 与 front-proxy 证书
结论
非 kubeadm 的二进制部署集群装 metrics-server,kubectl top 报 error: Metrics API not available。根因通常不在 metrics-server,而在 kube-apiserver 没配 API 聚合(aggregation layer)——缺 front-proxy CA,缺 --requestheader-* / --proxy-client-* 参数。
因果链(已验证):
apiserver 缺 --requestheader-client-ca-file / --proxy-client-cert-file
→ 不往 kube-system/extension-apiserver-authentication 写 requestheader-* 字段
→ 聚合 API Server(metrics-server)拿不到 CA,无法校验 apiserver 出示的客户端证书
→ 请求降级为匿名
→ APIService v1beta1.metrics.k8s.io 不可用 → kubectl top 报 Metrics API not availablekubeadm 部署不会遇到这个问题,因为 kubeadm 默认就生成 front-proxy CA 并写好这组参数。自己用二进制搭集群时,这层很容易整个漏掉。
适用范围与环境
| 项 | 值 |
|---|---|
| 集群形态 | 二进制部署(非 kubeadm),自研 k8s-installer 创建 |
| 节点 | VMware Workstation 单节点 lab,master 192.168.60.128 |
| OS / 运行时 | Ubuntu 26.04 / Docker 29.7.2 |
| Kubernetes | v1.35.4 |
| metrics-server | v0.9.0 |
同样适用于任何「手动部署 kube-apiserver」的场景,包括 kubeasz、二进制脚本、自研安装器。
症状
bash
$ kubectl top nodes
error: Metrics API not availablebash
$ kubectl -n kube-system get pods
NAME READY STATUS RESTARTS
metrics-server-67cbccccd9-d225m 0/1 Running 0metrics-server 容器日志(关键行):
text
E0910 11:49:11.970147 1 configmap_cafile_content.go:246] "Unhandled Error"
err="key failed with : missing content for CA bundle
\"client-ca::kube-system::extension-apiserver-authentication::requestheader-client-ca-file\""
logger="UnhandledError"注意:Starting RequestHeaderAuthRequestController 这类 "Starting controller" 日志只说明它在监听那个 ConfigMap key,不代表 key 存在。要判断是否修好,必须查实际状态(见下)。
根因
Kubernetes 的聚合层让 apiserver 能把某些 API 请求代理给独立的 API Server(metrics-server、custom metrics、service catalog 等)。这条链需要双向 TLS:
- apiserver 作为代理客户端,向聚合 API Server 出示 front-proxy-client 证书(
--proxy-client-cert-file/--proxy-client-key-file) - 聚合 API Server 用 front-proxy CA 校验它(apiserver 通过
extension-apiserver-authenticationConfigMap 把这个 CA 广播出去) - 校验通过后,apiserver 把用户身份塞进
X-Remote-User/X-Remote-Group等头传过去(--requestheader-username-headers等)
缺任何一环,聚合请求都降级为匿名,APIService 直接不可用。
kubectl -n kube-system get cm extension-apiserver-authentication -o yaml 就是这套机制的落点。修好前它只有 client-ca-file 一个 key;修好后会多出 5 个 requestheader-*。
修复步骤
方案 A:改安装器(推荐,一次修好后续重建都对)
以本项目 k8s-installer 为例,在控制面阶段补两处(commit 015aaa3):
1. 生成/加载 front-proxy CA,签一张 client 证书
go
// 本地目录与 pki/etcd、pki/k8s 平级
frontProxyDir := filepath.Dir(k8sPkiDir) + "/front-proxy"
// 必须幂等:重跑若换了 CA,apiserver 里的 requestheader CA
// 会和已下发的 client 证书对不上
fpCA, err := pki.LoadCA(frontProxyDir)
if err != nil {
fpCA, err = pki.NewCA(frontProxyDir, "front-proxy-ca")
if err != nil {
return fmt.Errorf("生成 front-proxy CA 失败: %w", err)
}
}
// CN 必须字面等于 apiserver 的 --requestheader-allowed-names
fpClientCert, fpClientKey, err := fpCA.Sign("front-proxy-client", nil, nil, nil)下发 3 个文件(CA 私钥不下发——apiserver 只需要 CA 证书来校验客户端):
| 文件 | 权限 |
|---|---|
/etc/kubernetes/pki/front-proxy-ca.pem | 0644 |
/etc/kubernetes/pki/front-proxy-client.pem | 0644 |
/etc/kubernetes/pki/front-proxy-client-key.pem | 0600 |
2. apiserver 加 7 个启动参数
ini
--proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.pem \
--proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client-key.pem \
--requestheader-allowed-names=front-proxy-client \
--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.pem \
--requestheader-extra-headers-prefix=X-Remote-Extra- \
--requestheader-group-headers=X-Remote-Group \
--requestheader-username-headers=X-Remote-User \改完 reset + init 重建集群(或重启 apiserver)。
方案 B:手工修一台已存在的集群
在任一机器上生成 front-proxy CA 与 client 证书(可用
openssl或cfssl),要求:- client 证书
CN=front-proxy-client - CA 与 client 证书的 EKU 都要能被 clientAuth 用途校验
- client 证书
把证书放到 master 的
/etc/kubernetes/pki/,按上表改权限。在
/etc/kubernetes/manifests/kube-apiserver.yaml(或 systemd unit / 静态 Pod 清单)里加上那 7 个参数。重启 kube-apiserver 静态 Pod。
让 metrics-server 重新读 ConfigMap(
kubectl -n kube-system rollout restart deploy/metrics-server)。
验证方法与实际结果
一键自检:apiserver 聚合参数是否就位
不用等 metrics-server 起来,这条最快(已验证):
bash
kubectl -n kube-system get cm extension-apiserver-authentication -o yaml \
| grep -E '^ [a-zA-Z-]+:' | sed 's/:.*//; s/^ //'修好前只有 client-ca-file;修好后输出:
text
client-ca-file
requestheader-allowed-names
requestheader-client-ca-file
requestheader-extra-headers-prefix
requestheader-group-headers
requestheader-username-headers完整验收(重建集群后实测)
| 检查项 | 结果 |
|---|---|
kubectl get apiservice v1beta1.metrics.k8s.io | AVAILABLE=True(原为不可用) |
extension-apiserver-authentication key | client-ca-file + 5 个 requestheader-* 全齐 |
| apiserver 启动参数 | 7 个新 flag 逐字对上模板 |
front-proxy-ca.pem / front-proxy-client.pem | 0644 |
front-proxy-client-key.pem | 0600 |
front-proxy-ca-key.pem | 不存在(按设计不下发) |
kubectl top nodes | master 278m / 1275Mi(原为 Metrics API not available) |
kubectl get apiservice 的 AVAILABLE 列是最直接的信号——它由 apiserver 探活聚合 API Server 得出,聚合链任一环断裂都会让它变成 False。
风险与边界
kubelet serving 证书是自签的
二进制部署(不走 TLS bootstrap)时,kubelet 会自己现造一张 serving 证书,不受集群 PKI 管理:
text
issuer=CN=master-ca@1789042785
subject=CN=master@1789042785因此 metrics-server 读 kubelet 需要加 --kubelet-insecure-tls(跳过校验)。同理,Prometheus 抓 kubelet 的 job 要带 insecure_skip_verify: true(已验证)。想彻底解决得给 kubelet 签发正式 serving 证书,是另一个话题。
metrics-server 用 Deployment,单副本足够
指标组件不要用 DaemonSet,除非采集的是本机数据:
| 组件 | 形态 | 理由 |
|---|---|---|
| node-exporter | DaemonSet | 采集本机数据,必须贴着主机跑 |
| kube-state-metrics | Deployment | watch 整个集群 API 对象,天生全局视角 |
| metrics-server | Deployment | 以 aggregated APIServer 身份注册,Service 后面挂一个副本就够 |
把 kube-state-metrics / metrics-server 改成每节点一个,只会让同一批指标产生 N 套完全相同的序列(Prometheus 当 N 个 target,Grafana 上 sum() 直接翻 N 倍),并让每个副本各开一份全量 list/watch,白烧 apiserver,收益为 0。真要高可用是 replicas: 2 + podAntiAffinity + PDB,不是 DaemonSet。
(StatefulSet 与「每节点一个」无关,它保证的是序号和存储的稳定性,不保证 Pod 落在哪台机器。)
重建集群时 CA 复用行为
安装器的 LoadCA 会复用本地 pki/ 里已有的 CA,所以重建集群时 kubernetes-ca / etcd-ca 不变,只有叶子证书重新签发;front-proxy CA 若本地已存在同样复用,不存在才新建。想让三套 CA 一起换新,得先删掉本地 pki/ 目录。
踩坑记录
- 把 metrics-server 的日志当作判断依据:
Starting RequestHeaderAuthRequestController只是 controller 启动,不代表 ConfigMap 里的 key 存在。要查extension-apiserver-authentication的实际 key 清单或 APIService 的AVAILABLE。 front-proxy-ca-key.pem被误下发:apiserver 只需要 CA 证书来校验客户端,不需要私钥。多下发一把 10 年有效期的 CA 私钥到节点是没必要的暴露。front-proxy-client的 CN 写错:必须字面等于--requestheader-allowed-names的值,否则 apiserver 校验客户端证书时会因 CN 不在允许列表而拒绝。- 顺序依赖:必须先让 apiserver 起来并写出 ConfigMap,metrics-server 才能读到 CA;不要反过来先调 metrics-server。