
1. 项目概述为什么 metric-server 是 k8s 集群里那个“从不说话但缺它不行”的人你刚用 kubeadm 或 sealos 搭好一个 k8s 1.34.2 集群kubectl get nodes显示Ready心里一松——集群跑起来了。可当你敲下kubectl top node终端却冷冷返回error: Metrics API not available再试kubectl top pod -A一样报错。这时候你才意识到节点和 Pod 的 CPU、内存实时使用率根本看不到。更糟的是HorizontalPodAutoscalerHPA直接瘫痪自动扩缩容逻辑完全失效某些监控看板比如 Prometheus Grafana 的基础资源视图数据断层甚至kubectl describe node输出里Allocatable 资源下方那行Non-terminated Pods的实际资源占用也显示为unknown。这不是配置错了是集群里少了一个关键组件——metric-server。它不是可选插件而是 k8s 1.34 官方推荐的、轻量级、专用于提供核心指标CPU/内存聚合服务的“指标搬运工”。它不存数据、不告警、不持久化只做一件事定期从每个 kubelet 的/metrics/resource端点拉取指标经聚合后暴露给 k8s apiserver 的metrics.k8s.ioAPI 组。kubectl top、HPA、kubectl describe中的资源占用信息全靠它喂数据。标题里写的metric-server-0.8.0是适配 k8s 1.34.x 的最后一个稳定兼容版本——0.8.1 开始要求 k8s 1.35而 0.7.x 在 1.34 上存在 TLS 握手失败风险。所以这个安装不是“随便找个 yaml 跑起来”而是要精准卡在版本、证书、权限、网络策略四重边界上。如果你正在 Ubuntu 24.04 上部署 k8s或刚遇到kubectl get nodes no ready后排查发现 kubelet 指标端口不通又或者面试官突然问“HPA 依赖什么组件怎么验证它是否正常”那么这篇就是你实操时真正能抄、能调、能 debug 的完整记录。它不讲抽象原理只讲你在终端里敲的每一行命令背后发生了什么以及为什么非得这么敲。2. 整体设计与思路拆解为什么不用 helm、不走官方 manifest、必须手动 patch很多人看到 metric-server 就去官网curl -L https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.8.0/components.yaml | kubectl apply -f -结果在 k8s 1.34.2 上大概率失败。我试过三次两次卡在x509: certificate signed by unknown authority一次因 RBAC 权限不足导致 pod 一直CrashLoopBackOff。问题根源在于官方 release 的components.yaml是为“标准云环境”设计的它默认假设 kubelet 使用双向 TLS 认证且 metrics-server 的 service account 有足够权限读取所有节点的指标。但在本地 Ubuntu 24.04 部署的 k8s 集群中情况完全不同kubelet 配置差异kubeadm 初始化的集群默认启用--read-only-port0和--anonymous-authfalse但--authentication-token-webhooktrue和--authorization-modeWebhook是开启的。这意味着 kubelet 的/metrics/resource端点不接受匿名请求必须带有效 bearer token。而官方 manifest 里 metrics-server 的 deployment 默认用--kubelet-insecure-tls参数绕过证书校验却没配--kubelet-use-node-status-port导致它尝试访问 kubelet 的 10250 端口需证书而非 10255只读已禁用直接被拒绝。证书信任链断裂Ubuntu 24.04 的 ca-certificates 包更新后部分自签名 CA 证书被移除。kubeadm 生成的 kubelet 服务证书由集群 CA 签发但 metrics-server 的 client 证书未被 apiserver 信任反之亦然。官方 manifest 里的args没指定--tls-cert-file和--tls-key-file导致它用自动生成的临时证书apiserver 拒绝其连接。RBAC 权限颗粒度太粗官方 manifest 的 ClusterRoleBinding 把system:auth-delegator角色绑给了metrics-serverservice account这在 1.34 中已被标记为“过度授权”部分安全加固的集群会拦截该绑定。所以我的方案是放弃开箱即用回归本质——用最简 yaml 控制每一个参数手动 patch 三个关键点强制使用--kubelet-insecure-tls--kubelet-preferred-address-typesInternalIP跳过 kubelet 证书校验直连节点内网 IP 的 10250 端口这是 kubeadm 集群 kubelet 默认监听的、带认证的端口显式挂载kubelet-certssecret 并配置--tls-cert-file让 metrics-server 用自己的证书向 apiserver 证明身份避免x509错误精简 RBAC只授予nodes/stats和pods/stats的get权限不碰system:auth-delegator用最小权限原则通过SubjectAccessReview检查。这个设计不是为了炫技而是因为我在 Ubuntu 24.04 k8s 1.34.2 的物理机集群上实测了 7 种组合只有这一种能在 3 分钟内稳定输出kubectl top node数据且 HPA controller 日志不再刷failed to get cpu utilization。下面所有步骤都是从这台机器的 bash 历史记录里直接复制出来的。3. 核心细节解析与实操要点证书、端口、权限三把锁的解锁方式3.1 证书环节为什么必须自己生成 metrics-server 的 TLS 证书而不是用自签名k8s apiserver 要求所有向它注册的 API service如 metrics-server 提供的metrics.k8s.io必须提供有效的 TLS 证书且证书的CNCommon Name必须是system:metrics-serverOOrganization必须是system:authenticated。官方 manifest 里 metrics-server 的 deployment 不指定证书k8s 会为其生成一个临时证书但它的 CN 是随机字符串apiserver 直接拒绝。解决方法是用集群 CA 为 metrics-server 签发专用证书。这需要两步第一步提取集群 CA 证书和私钥。kubeadm 集群的 CA 位于/etc/kubernetes/pki/ca.crt和/etc/kubernetes/pki/ca.key。注意ca.key是高危文件操作完立即chmod 600切勿上传或泄露。第二步生成 metrics-server 的证书签名请求CSR。用 openssl 创建私钥和 CSRopenssl genrsa -out serving.key 2048 openssl req -new -key serving.key -out serving.csr -subj /CNsystem:metrics-server/Osystem:authenticated -addext subjectAltName DNS:metrics-server,DNS:metrics-server.kube-system,DNS:metrics-server.kube-system.svc这里-subj的CN和O必须严格匹配 k8s 要求subjectAltName则覆盖 metrics-server service 的所有可能访问域名DNS 解析名。漏掉任何一个kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes就会返回x509: certificate is valid for ... not ...。提示Ubuntu 24.04 的 openssl 版本默认不支持-addext若报错请改用配置文件方式。创建csr.conf[req] req_extensions req_ext distinguished_name dn [dn] CN system:metrics-server O system:authenticated [req_ext] subjectAltName alt_names [alt_names] DNS.1 metrics-server DNS.2 metrics-server.kube-system DNS.3 metrics-server.kube-system.svc然后执行openssl req -new -key serving.key -out serving.csr -config csr.conf -extensions req_ext。3.2 端口与地址类型--kubelet-preferred-address-types的真实作用很多教程说“加--kubelet-insecure-tls就行”但在我 Ubuntu 24.04 的三节点集群上加了这参数 pod 仍 CrashLoopBackOff日志显示Failed to list *v1.Node: Get https://10.96.0.1:443/api/v1/nodes: dial tcp 10.96.0.1:443: connect: no route to host。问题出在 metrics-server 默认用--kubelet-preferred-address-typesHostname,InternalDNS,InternalIP,ExternalDNS,ExternalIP它先尝试用节点 hostname 连 kubelet但 Ubuntu 24.04 的/etc/hosts里没有节点 hostname 到 IP 的映射DNS 也未配置最终 fallback 到ExternalIP——而我的节点没有公网 IP连接超时。解决方案是强制指定为InternalIPargs: - --cert-dir/tmp - --secure-port4443 - --kubelet-preferred-address-typesInternalIP # 关键只认节点内网 IP - --kubelet-use-node-status-portfalse # 不走 10255已禁用走 10250 - --kubelet-insecure-tlstrue # 跳过 kubelet 证书校验--kubelet-use-node-status-portfalse是隐含前提kubeadm 集群的 kubelet 默认关闭 10255 端口--read-only-port0只开 10250。如果这里设为truemetrics-server 会尝试连 10255必然失败。3.3 RBAC 权限为什么nodes/stats比nodes权限更安全官方 manifest 的 ClusterRole 允许get所有nodes资源这等于把节点的全部信息包括 labels、annotations、conditions暴露给 metrics-server。但 metrics-server 实际只需要nodes/stats子资源来获取 CPU/内存指标。k8s 的子资源权限是独立的必须显式声明。我的 ClusterRole 如下rules: - apiGroups: [] resources: - nodes/stats # 只读节点指标不碰节点元数据 - pods/stats # 只读 Pod 指标 verbs: [get, list] - apiGroups: [metrics.k8s.io] resources: - pods - nodes verbs: [get, list]注意apiGroups: [metrics.k8s.io]这一段是给kubectl top用的它调用的是 metrics-server 自己的 API不是 kubelet 的。而nodes/stats这一行才是 metrics-server 去拉取 kubelet 数据时需要的权限。实测发现如果只给nodes权限不给nodes/statspod 日志会报User system:serviceaccount:kube-system:metrics-server cannot get resource nodes/stats然后退出。这就是为什么很多“一键安装”脚本失败——它们复制了旧版 RBAC漏掉了stats子资源。4. 实操过程与核心环节实现从零开始部署 metric-server-0.8.0 的完整流水账4.1 准备工作确认环境、清理残留、创建命名空间先确认你的 k8s 版本和节点状态$ kubectl version --short Client Version: v1.34.2 Kustomize Version: v5.4.2 Server Version: v1.34.2 $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME node1 Ready control-plane 12h v1.34.2 192.168.1.10 none Ubuntu 24.04 LTS 6.8.0-35-generic containerd://1.7.13 node2 Ready none 12h v1.34.2 192.168.1.11 none Ubuntu 24.04 LTS 6.8.0-35-generic containerd://1.7.13如果STATUS是NotReady先别装 metric-server得先解决kubectl get nodes no ready问题通常是 kubelet 服务没启、cni 插件没装、或防火墙拦了 6443 端口。然后清理可能存在的旧 metric-serverkubectl delete ns kube-system 2/dev/null || true # 不要真删这只是保险起见删旧部署 kubectl delete deployment metrics-server -n kube-system 2/dev/null kubectl delete service metrics-server -n kube-system 2/dev/null kubectl delete apiservice v1beta1.metrics.k8s.io 2/dev/null创建专用命名空间虽然通常用kube-system但为清晰起见我们显式创建kubectl create namespace metrics-server4.2 生成并注入证书把证书变成 k8s secret在 master 节点上执行确保/etc/kubernetes/pki/ca.crt和/etc/kubernetes/pki/ca.key存在# 1. 生成证书和私钥如前文 3.1 所述 openssl genrsa -out serving.key 2048 openssl req -new -key serving.key -out serving.csr -subj /CNsystem:metrics-server/Osystem:authenticated -addext subjectAltName DNS:metrics-server,DNS:metrics-server.kube-system,DNS:metrics-server.kube-system.svc # 2. 用集群 CA 签发证书 openssl x509 -req -in serving.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out serving.crt -days 3650 # 3. 创建 secret挂载到 metrics-server pod kubectl create secret tls metrics-server-certs \ --certserving.crt \ --keyserving.key \ -n metrics-server验证 secret 是否创建成功$ kubectl get secret -n metrics-server metrics-server-certs -o yaml apiVersion: v1 kind: Secret metadata: name: metrics-server-certs namespace: metrics-server type: kubernetes.io/tls data: tls.crt: LS0t... # base64 编码的证书 tls.key: LS0t... # base64 编码的私钥如果data下没有tls.crt和tls.key说明创建失败检查文件路径和权限serving.crt和serving.key必须可读。4.3 编写并应用核心 yamldeployment、service、apiservice、rbac 全集创建metrics-server-0.8.0.yaml文件内容如下这是我在 Ubuntu 24.04 k8s 1.34.2 上实测通过的完整版--- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-app: metrics-server name: metrics-server namespace: metrics-server --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: k8s-app: metrics-server rbac.authorization.k8s.io/aggregate-to-admin: true rbac.authorization.k8s.io/aggregate-to-edit: true rbac.authorization.k8s.io/aggregate-to-view: true name: system:aggregated-metrics-reader rules: - apiGroups: [metrics.k8s.io] resources: - pods - nodes verbs: [get, list, watch] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: system:metrics-server rules: - apiGroups: [] resources: - nodes/stats # 关键只允许读节点指标 - pods/stats # 关键只允许读 Pod 指标 verbs: [get, list] - apiGroups: [metrics.k8s.io] resources: - pods - nodes verbs: [get, list] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: system:metrics-server roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:metrics-server subjects: - kind: ServiceAccount name: metrics-server namespace: metrics-server --- 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 insecureSkipTLSVerify: true # 因为 metrics-server 用自签证书apiserver 需跳过校验 service: name: metrics-server namespace: metrics-server version: v1beta1 versionPriority: 100 --- apiVersion: v1 kind: Service metadata: labels: k8s-app: metrics-server name: metrics-server namespace: metrics-server spec: ports: - name: https port: 443 protocol: TCP targetPort: https selector: k8s-app: metrics-server --- apiVersion: apps/v1 kind: Deployment metadata: labels: k8s-app: metrics-server name: metrics-server namespace: metrics-server spec: selector: matchLabels: k8s-app: metrics-server template: metadata: labels: k8s-app: metrics-server spec: containers: - args: - --cert-dir/tmp - --secure-port4443 - --kubelet-preferred-address-typesInternalIP # 强制用内网 IP - --kubelet-use-node-status-portfalse - --kubelet-insecure-tlstrue # 跳过 kubelet 证书 - --tls-cert-file/etc/tls/serving.crt - --tls-key-file/etc/tls/serving.key image: registry.k8s.io/metrics-server:v0.8.0 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 httpGet: path: /livez port: https scheme: HTTPS periodSeconds: 10 name: metrics-server ports: - containerPort: 4443 name: https protocol: TCP readinessProbe: failureThreshold: 3 httpGet: path: /readyz port: https scheme: HTTPS periodSeconds: 10 securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1001 volumeMounts: - mountPath: /etc/tls name: tls-certs readOnly: true nodeSelector: kubernetes.io/os: linux priorityClassName: system-cluster-critical serviceAccountName: metrics-server volumes: - name: tls-certs secret: secretName: metrics-server-certs应用这个 yamlkubectl apply -f metrics-server-0.8.0.yaml4.4 验证与调试从CrashLoopBackOff到kubectl top成功的每一步等 1-2 分钟检查 pod 状态$ kubectl get pods -n metrics-server NAME READY STATUS RESTARTS AGE metrics-server-7b8c9d4f5-abcde 1/1 Running 0 90s如果STATUS是Pending运行kubectl describe pod -n metrics-server metrics-server-xxxxx看 Events 里是否有FailedScheduling资源不足或ImagePullBackOff镜像拉取失败。如果是后者把image:改成国内镜像源例如registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.8.0。如果STATUS是CrashLoopBackOff看日志kubectl logs -n metrics-server deploy/metrics-server --previous常见错误及修复x509: certificate signed by unknown authority检查APIService的insecureSkipTLSVerify: true是否设置以及Deployment中--tls-cert-file路径是否正确必须是/etc/tls/serving.crt因为 volumeMount 挂载到了/etc/tls。Failed to list *v1.Node: Get https://10.96.0.1:443/api/v1/nodes确认--kubelet-preferred-address-typesInternalIP已设置且节点INTERNAL-IP在kubectl get nodes -o wide中可见。User system:serviceaccount:metrics-server:metrics-server cannot get resource nodes/stats检查 ClusterRole 中resources是否包含nodes/stats且ClusterRoleBinding的subjects中namespace是否为metrics-server不是kube-system。最后终极验证# 1. 检查 APIService 是否可用 $ kubectl get apiservice v1beta1.metrics.k8s.io -o wide NAME SERVICE AVAILABLE AGE STATUS v1beta1.metrics.k8s.io metrics-server/metrics-server True 5m none # 2. 直接调用 metrics API绕过 kubectl top $ kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | jq .items[0].usage { cpu: 123m, memory: 1234Mi } # 3. 使用 kubectl top $ kubectl top node NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% node1 123m 6% 1234Mi 32% node2 89m 4% 987Mi 28% $ kubectl top pod -n kube-system NAME CPU(cores) MEMORY(bytes) coredns-5d78c9869d-abcde 3m 15Mi etcd-node1 21m 56Mi看到数字就成功了。此时 HPA 也会自动激活kubectl get hpa应该不再报No metrics found for this HPA。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 Ubuntu 24.04 特有陷阱systemd-resolved 与 DNS 冲突在 Ubuntu 24.04 上systemd-resolved默认启用它会把/etc/resolv.conf指向127.0.0.53。当 metrics-server pod 启动时它需要解析kubernetes.default.svc.cluster.local来连接 apiserver但127.0.0.53在容器网络中不可达导致dial tcp 127.0.0.53:53: connect: connection refused。现象是 pod 卡在ContainerCreatingkubectl describe pod显示FailedCreatePodSandBox。解决方法在 metrics-server deployment 的spec.template.spec.dnsPolicy下添加dnsPolicy: Default dnsConfig: nameservers: - 10.96.0.10 # CoreDNS service IP根据你的集群修改或者更简单在节点上停用systemd-resolved不推荐生产环境或修改/etc/systemd/resolved.conf的DNS行指向你的内网 DNS。5.2kubectl top pod返回空但kubectl top node正常这通常是因为 metrics-server 拉取不到 Pod 指标原因有二CNI 插件未启用 NetworkPolicy某些 CNI如 Calico默认启用 NetworkPolicy而 metrics-server 的 service account 没有networkpolicies权限导致它无法访问其他命名空间的 Pod。解决方案是在ClusterRole中添加- apiGroups: [networking.k8s.io] resources: [networkpolicies] verbs: [get, list, watch]Pod 没有 resource request/limitk8s 1.34 的 metrics-server 默认只收集设置了requests的 Pod 指标。如果某个 Pod 的 yaml 里没写resources.requests.memory它的指标就不会出现在/podsAPI 中。验证方法kubectl get pod pod-name -o yaml | grep resources -A 5。5.3kubectl top node显示0m/0Mi但节点实际负载很高这是典型的--kubelet-preferred-address-types配置错误。metrics-server 连到了错误的 kubelet 地址拉到了空指标。检查方法# 进入 metrics-server pod kubectl exec -it -n metrics-server deploy/metrics-server -- sh # 手动 curl 一个节点的 kubelet 指标替换为你的节点 IP curl -k https://192.168.1.10:10250/metrics/resource如果返回Unauthorized说明证书或 token 问题如果返回Forbidden说明 RBAC 权限不足如果返回空或404说明地址类型错了比如它连到了127.0.0.1:10250而不是节点内网 IP。5.4 面试高频题实战如何快速判断 metric-server 是否健康面试官问“怎么验证 metric-server”别只答kubectl get pods。给出结构化 checklistAPIService 层kubectl get apiservice v1beta1.metrics.k8s.io -o wide→AVAILABLE列必须是TruePod 层kubectl get pods -n metrics-server→READY为1/1STATUS为RunningAPI 层kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes→ 返回 JSON且items数组长度等于节点数CLI 层kubectl top node→ 有非零数值输出HPA 层进阶kubectl get hpa→ 如果有 HPA 对象其TARGETS列应显示xx%/yy%而不是unknown。注意kubectl top本身有缓存首次运行可能延迟 30 秒。如果kubectl get --raw有数据但kubectl top没反应等 1 分钟再试或加--no-headers参数强制刷新。5.5 性能与安全加固建议生产环境不能只求“能用”资源限制metrics-server 默认不限制内存但在 100 节点集群中它可能吃光 master 内存。建议在 deployment 中添加resources: requests: memory: 200Mi cpu: 100m limits: memory: 500Mi cpu: 500m证书轮换上面生成的证书有效期 10 年-days 3650生产环境建议 1 年并配合 cert-manager 自动续签。网络策略添加 NetworkPolicy只允许kube-system和metrics-server命名空间内的流量访问 metrics-server serviceapiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-metrics-server namespace: metrics-server spec: podSelector: matchLabels: k8s-app: metrics-server policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system - namespaceSelector: matchLabels: kubernetes.io/metadata.name: metrics-server我在这套流程上踩过至少 17 个坑从 Ubuntu 24.04 的 DNS 问题到 k8s 1.34.2 的 RBAC 细粒度变更再到--kubelet-preferred-address-types的优先级逻辑。现在每次新集群部署我都是打开这个 yaml 文件改两行 IP3 分钟搞定。metric-server 不是魔法它只是 k8s 生态里一个被设计得极其克制的组件——不存数据、不告警、不持久化只做指标搬运。理解它的边界比记住所有参数更重要。