跳转到内容

二进制集群装 metrics-server:聚合 API 与 front-proxy 证书

结论

非 kubeadm 的二进制部署集群装 metrics-server,kubectl toperror: 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 available

kubeadm 部署不会遇到这个问题,因为 kubeadm 默认就生成 front-proxy CA 并写好这组参数。自己用二进制搭集群时,这层很容易整个漏掉。

适用范围与环境

集群形态二进制部署(非 kubeadm),自研 k8s-installer 创建
节点VMware Workstation 单节点 lab,master 192.168.60.128
OS / 运行时Ubuntu 26.04 / Docker 29.7.2
Kubernetesv1.35.4
metrics-serverv0.9.0

同样适用于任何「手动部署 kube-apiserver」的场景,包括 kubeasz、二进制脚本、自研安装器。

症状

bash
$ kubectl top nodes
error: Metrics API not available
bash
$ kubectl -n kube-system get pods
NAME                             READY   STATUS    RESTARTS
metrics-server-67cbccccd9-d225m  0/1     Running   0

metrics-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-authentication ConfigMap 把这个 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.pem0644
/etc/kubernetes/pki/front-proxy-client.pem0644
/etc/kubernetes/pki/front-proxy-client-key.pem0600

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:手工修一台已存在的集群

  1. 在任一机器上生成 front-proxy CA 与 client 证书(可用 opensslcfssl),要求:

    • client 证书 CN=front-proxy-client
    • CA 与 client 证书的 EKU 都要能被 clientAuth 用途校验
  2. 把证书放到 master 的 /etc/kubernetes/pki/,按上表改权限。

  3. /etc/kubernetes/manifests/kube-apiserver.yaml(或 systemd unit / 静态 Pod 清单)里加上那 7 个参数。

  4. 重启 kube-apiserver 静态 Pod。

  5. 让 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.ioAVAILABLE=True(原为不可用)
extension-apiserver-authentication keyclient-ca-file + 5 个 requestheader-* 全齐
apiserver 启动参数7 个新 flag 逐字对上模板
front-proxy-ca.pem / front-proxy-client.pem0644
front-proxy-client-key.pem0600
front-proxy-ca-key.pem不存在(按设计不下发)
kubectl top nodesmaster 278m / 1275Mi(原为 Metrics API not available

kubectl get apiserviceAVAILABLE 列是最直接的信号——它由 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-exporterDaemonSet采集本机数据,必须贴着主机跑
kube-state-metricsDeploymentwatch 整个集群 API 对象,天生全局视角
metrics-serverDeployment以 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/ 目录。

踩坑记录

  1. 把 metrics-server 的日志当作判断依据Starting RequestHeaderAuthRequestController 只是 controller 启动,不代表 ConfigMap 里的 key 存在。要查 extension-apiserver-authentication 的实际 key 清单或 APIService 的 AVAILABLE
  2. front-proxy-ca-key.pem 被误下发:apiserver 只需要 CA 证书来校验客户端,不需要私钥。多下发一把 10 年有效期的 CA 私钥到节点是没必要的暴露。
  3. front-proxy-client 的 CN 写错:必须字面等于 --requestheader-allowed-names 的值,否则 apiserver 校验客户端证书时会因 CN 不在允许列表而拒绝。
  4. 顺序依赖:必须先让 apiserver 起来并写出 ConfigMap,metrics-server 才能读到 CA;不要反过来先调 metrics-server。

基于 MIT 许可发布