Kubernetes metadata-proxy 组件解析:拦截 GCE 元数据服务、向 Pod 隐藏 kubelet 的 kube-env 凭据
导读:metadata-proxy 是 Kubernetes 仓库中为 GCE/GKE 类云环境准备的集群级 addon,它以 DaemonSet 形式在每个节点上拦截 Pod 对云平台元数据服务(169.254.169.254)的访问:对 kubelet 初始化所需的 kube-env 敏感数据一律返回 403,其余请求则照常转发给元数据服务器。读完本文你将掌握该组件的核心设计(返回 403 的隔离策略)、完整部署清单逐项拆解、集群启动脚本中的开关与节点标签联动,以及底层 iptables 流量接管原理。
一、组件定位:解决什么问题
在 GCE 等云平台上,每个虚拟机都能访问链路本地地址 169.254.169.254 上的实例元数据服务。元数据中除了实例信息外,还包含 kubelet 引导节点所需的 kube-env 数据,其中携带节点加入集群用的敏感凭据。若集群中运行的 Pod 也能直接读取这些元数据,就存在凭据泄露面——任何被攻破或恶意的容器都可能借此拿到节点级凭据。
metadata-proxy 的说明文档 对该组件的职责给出了精炼定义:
This metadata proxy returns a 403 for kubelet's kube-env data, but otherwise allows pods access to the metadata server.
即:对 kubelet 的 kube-env 数据返回 HTTP 403,除此之外,允许 Pod 正常访问元数据服务器。这是典型的“最小特权”做法——不去切断 Pod 对元数据服务的合法访问(某些 Pod 依赖元数据做云厂商能力发现、实例标签读取等),而是只对含敏感凭据的 kube-env 路径做定向封禁。该组件正是集群启动配置中 ENABLE_METADATA_CONCEALMENT(元数据隐藏)特性的实现载体。
二、部署形态:一份清单 = ServiceAccount + DaemonSet
组件清单位于 cluster/addons/metadata-proxy/gce/metadata-proxy.yaml,由两个资源构成:一个用于运行身份的 ServiceAccount,以及一个确保“每个节点一个代理实例”的 DaemonSet。
2.1 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: metadata-proxy
namespace: kube-system
labels:
k8s-app: metadata-proxy
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
需要注意标签 addonmanager.kubernetes.io/mode: Reconcile:这是 addon-manager 的声明式管理模式标志,表明该资源由 kube-addon-manager 持续调和(Reconcile),清单内容与集群实际状态不一致时会被自动修正。
2.2 DaemonSet 关键配置
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: metadata-proxy-v0.1
namespace: kube-system
labels:
k8s-app: metadata-proxy
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
version: v0.1
spec:
selector:
matchLabels:
k8s-app: metadata-proxy
version: v0.1
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
k8s-app: metadata-proxy
kubernetes.io/cluster-service: "true"
version: v0.1
spec:
priorityClassName: system-node-critical
serviceAccountName: metadata-proxy
hostNetwork: true
dnsPolicy: Default
tolerations:
- operator: "Exists"
effect: "NoExecute"
- operator: "Exists"
effect: "NoSchedule"
containers:
- name: metadata-proxy
image: registry.k8s.io/metadata-proxy:v0.1.12
args: ["--addr=0.0.0.0:988"]
securityContext:
privileged: true
resources:
requests:
memory: "25Mi"
cpu: "30m"
limits:
memory: "25Mi"
cpu: "30m"
nodeSelector:
cloud.google.com/metadata-proxy-ready: "true"
kubernetes.io/os: linux
terminationGracePeriodSeconds: 30
逐项理解这份清单的设计意图:
| 配置项 | 取值 | 作用与影响 |
|---|---|---|
hostNetwork: true |
布尔 | 代理直接使用宿主机网络栈,从而能绑定链路本地地址、接收被 iptables DNAT 转来的流量;监听地址由 --addr=0.0.0.0:988 指定在 988 端口 |
dnsPolicy: Default |
策略 | hostNetwork 下使用节点默认 DNS 配置 |
securityContext.privileged: true |
布尔 | 代理需要对网络/路由做底层操作(监听链路本地地址、处理转发流量)而授予特权 |
priorityClassName: system-node-critical |
优先级 | 属于节点关键系统组件,资源紧张时优先保证调度与存活 |
tolerations(NoExecute/NoSchedule,operator: Exists) |
容忍 | 允许调度/运行到存在污点的节点,保证全节点覆盖 |
nodeSelector.cloud.google.com/metadata-proxy-ready: "true" |
标签 | 只在节点被显式标记为“已就绪”后调度,实现与防火墙规则部署顺序解耦(见下文第五节) |
resources(requests=limits,25Mi / 30m) |
配额 | 请求与限制相等,Pod 获得 Guaranteed QoS,避免被驱逐 |
terminationGracePeriodSeconds: 30 |
秒 | 滚动更新时给代理 30 秒优雅退出时间 |
updateStrategy: RollingUpdate |
策略 | 版本升级(如 v0.1 → v0.2)时采用滚动替换而非全量重建 |
2.3 可选的 prometheus-to-sd 监控边车
清单中以注释块 # BEGIN_PROMETHEUS_TO_SD … # END_PROMETHEUS_TO_SD 包围着第二个容器,它不是代理本体,而是把指标推送到 Google Cloud Monitoring(Stackdriver)的边车:
- name: prometheus-to-sd-exporter
image: gke.gcr.io/prometheus-to-sd:v0.11.1-gke.1
resources:
requests: { memory: "20Mi", cpu: "2m" }
limits: { memory: "20Mi", cpu: "2m" }
command:
- /monitor
- --stackdriver-prefix={{ prometheus_to_sd_prefix }}/addons
- --api-override={{ prometheus_to_sd_endpoint }}
- --source=metadata_proxy:http://127.0.0.1:989?whitelisted=request_count
- --pod-id=$(POD_NAME)
- --namespace-id=$(POD_NAMESPACE)
关键信息:代理本体在 989 端口暴露了 Prometheus 格式的监控端点,边车只采集 request_count(请求计数)这一指标,通过 POD_NAME/POD_NAMESPACE(来自 fieldRef 的 Downward API)标注来源。两个模板变量 {{ prometheus_to_sd_prefix }} 与 {{ prometheus_to_sd_endpoint }} 由部署脚本在执行时注入——即集群启动时调用的 update-daemon-set-prometheus-to-sd-parameters 函数。
三、集群启动脚本中的联动开关
3.1 特性开关与节点标签(config-default.sh)
该组件并非默认启用。在 cluster/gce/config-default.sh 中:
# TODO(#8867) Enable by default.
ENABLE_METADATA_CONCEALMENT="${ENABLE_METADATA_CONCEALMENT:-false}" # true, false
METADATA_CONCEALMENT_NO_FIREWALL="${METADATA_CONCEALMENT_NO_FIREWALL:-false}" # true, false
if [[ ${ENABLE_METADATA_CONCEALMENT:-} == "true" ]]; then
# Put the necessary label on the node so the daemonset gets scheduled.
NODE_LABELS="${NODE_LABELS},cloud.google.com/metadata-proxy-ready=true"
# Add to the provider custom variables.
PROVIDER_VARS="${PROVIDER_VARS:-} ENABLE_METADATA_CONCEALMENT METADATA_CONCEALMENT_NO_FIREWALL"
fi
可以总结出三条联动逻辑:
ENABLE_METADATA_CONCEALMENT(默认false) 是总开关,源码注释中的TODO(#8867)表明作者期望未来默认开启;而 cluster/gce/config-test.sh 中该值为true,即测试集群默认启用以覆盖验证。- 开启后,节点会被打上
cloud.google.com/metadata-proxy-ready=true标签——这正是 DaemonSet 的nodeSelector所依赖的标签,二者必须配合:只打标签而没开启则 DaemonSet 清单不会下发;只开启而没打标签则代理不会调度到节点。 METADATA_CONCEALMENT_NO_FIREWALL(默认false)用于部分节点不创建防火墙/接管规则的逃生开关,与代理的部署解耦。
3.2 清单下发与参数注入(configure-helper.sh)
GCE kube-up 集群在节点初始化脚本 cluster/gce/gci/configure-helper.sh 中完成部署:
if [[ "${ENABLE_METADATA_CONCEALMENT:-}" == "true" ]]; then
setup-addon-manifests "addons" "metadata-proxy/gce"
local -r metadata_proxy_yaml="${dst_dir}/metadata-proxy/gce/metadata-proxy.yaml"
update-daemon-set-prometheus-to-sd-parameters ${metadata_proxy_yaml}
done
即:先通过 setup-addon-manifests 把本组件的清单(相对路径 addons/metadata-proxy/gce)渲染到 addon 目录,再由 kube-addon-manager 依据 Reconcile 模式应用到集群;随后调用 update-daemon-set-prometheus-to-sd-parameters 把上一节提到的 Stackdriver 前缀/端点等模板参数替换进 YAML。
3.3 旧标签的平滑迁移
由于历史原因,早期版本使用 beta.kubernetes.io/metadata-proxy-ready 标签。节点初始化脚本通过 update-legacy-addon-node-labels 完成迁移(configure-helper.sh):
update-node-label "beta.kubernetes.io/metadata-proxy-ready=true,cloud.google.com/metadata-proxy-ready!=true" "cloud.google.com/metadata-proxy-ready=true"
该命令借助 kubectl 选择器(selector)找出仍带旧标签、尚未打新标签的节点,为其补上 cloud.google.com/metadata-proxy-ready=true。update-node-label 内部使用 kubectl label --overwrite nodes -l ... 并最多重试 5 次、每次间隔 3 秒,等待 kube-apiserver 就绪(外层先 until kubectl get nodes 轮询),保证 DaemonSet 能最终调度覆盖全部旧节点。
四、数据平面:iptables 如何把流量交给代理
代理能“隐身”拦截 Pod 流量的关键在于宿主机网络规则。同一份节点初始化脚本在配置网络时写入如下规则(configure-helper.sh):
if [[ "${ENABLE_METADATA_CONCEALMENT:-}" == "true" ]] && [[ ! "${METADATA_CONCEALMENT_NO_FIREWALL:-}" == "true" ]]; then
echo "Add rule for metadata concealment"
ip addr add dev lo 169.254.169.252/32 scope host
iptables -w -t nat -I PREROUTING -p tcp ! -i eth0 -d "${METADATA_SERVER_IP}" --dport 80 \
-m comment --comment "metadata-concealment: bridge traffic to metadata server goes to metadata proxy" \
-j DNAT --to-destination 169.254.169.252:988
iptables -w -t nat -I PREROUTING -p tcp ! -i eth0 -d "${METADATA_SERVER_IP}" --dport 8080 \
-m comment --comment "metadata-concealment: bridge traffic to metadata server goes to metadata proxy" \
-j DNAT --to-destination 169.254.169.252:987
fi
iptables -w -t mangle -I OUTPUT -s 169.254.169.254 -j DROP
这些规则的语义与 DaemonSet 配置一一对应,可以还原出完整的数据流:
- 分配链路本地虚拟地址:
ip addr add dev lo 169.254.169.252/32 scope host在宿主机的 loopback 接口上挂了一个内部地址169.254.169.252。由于代理以hostNetwork: true运行并监听0.0.0.0:988,它就能在宿主机命名空间内接收发往该地址的流量。 - DNAT 定向劫持:对非 eth0 入向(
! -i eth0过滤掉宿主机自身管理流量,只拦桥接/容器网络进入的流量)、目的为云元数据服务地址$METADATA_SERVER_IP(即169.254.169.254)的 TCP 连接:- 目标端口 80 被改写为
169.254.169.252:988——对应代理的--addr=0.0.0.0:988; - 目标端口 8080 被改写为
169.254.169.252:987。
- 目标端口 80 被改写为
- 封死源地址欺骗回程:
-t mangle OUTPUT -s 169.254.169.254 -j DROP丢弃任何声称来自元数据服务 IP 的出站包,杜绝绕过代理直接“钓鱼”返回路径。 - 代理决策:接管后的请求到达代理进程。代理对 kube-env 相关请求直接回 403,其余请求则由代理以宿主身份代为访问真正的元数据服务器并把结果回传——这正是 README 所述“403 隔离 + 其余放行”策略在转发层面(代理会代答/代转)的体现。
结合代码可推断整体架构:Pod → 元数据请求 → 节点 PREROUTING DNAT → hostNetwork 代理(169.254.169.252:988)→ 判定 kube-env 则 403,否则转发至 169.254.169.254:80 的元数据服务。代理与 kubelet 等系统组件运行在同一网络命名空间,因此 kubelet 读取 kube-env 走的是 eth0 直连路径,不受 ! -i eth0 规则影响。
五、运维要点与适用前提
适用前提:本文所述组件路径与配置均面向仓库自带的 GCE kube-up / 集群初始化体系(cluster/gce),核心事实依据为 cluster/addons/metadata-proxy/README.md 与 metadata-proxy.yaml,以及节点初始化脚本 configure-helper.sh 中的联动逻辑。托管型 GKE 的元数据隐藏由其自身控制面管理,不属于该仓库此清单的职责范围。
关键运维结论汇总:
- 默认不启用:
ENABLE_METADATA_CONCEALMENT默认false(config-test.sh 例外为true),开启需在 kube-up 配置中显式置为true。 - 两层保护缺一不可:DaemonSet 依赖
cloud.google.com/metadata-proxy-ready=true节点标签调度,而该标签由脚本在开关打开时统一注入;若标签缺失,代理不会运行,同时 iptables 仍可能把流量 DNAT 到无人监听的 988 端口,因此生产环境务必保证开关、标签、防火墙规则三者状态一致。 - 资源占用极低:代理本体 25Mi/30m,监控边车 20Mi/2m,均为 Guaranteed QoS;若不需要将
request_count指标推送至 Stackdriver,可依据注释块裁剪边车容器(需同步处理清单中的{{ prometheus_to_sd_* }}模板变量)。 - 版本演进方式:DaemonSet 名称携带版本号(
metadata-proxy-v0.1),镜像当前为registry.k8s.io/metadata-proxy:v0.1.12;升级通过改名称版本与滚动更新策略完成,旧版本实例由 addon-manager 按 Reconcile 模式清理。 - 故障排查切入点:可优先检查节点是否具备
cloud.google.com/metadata-proxy-ready=true标签、kubectl -n kube-system get ds metadata-proxy-v0.1是否满调度、iptables -t nat -S PREROUTING中两条metadata-concealment注释规则是否存在,以及代理容器日志中 403 命中情况(结合 989 端口的request_count指标)。
参考文件索引:
- 组件说明:cluster/addons/metadata-proxy/README.md
- 完整部署清单:cluster/addons/metadata-proxy/gce/metadata-proxy.yaml
- 默认集群配置(开关与标签):cluster/gce/config-default.sh
- 测试集群配置:cluster/gce/config-test.sh
- 节点初始化脚本(iptables 规则、清单下发、标签迁移):cluster/gce/gci/configure-helper.sh
说明:文中涉及组件镜像版本、端口、标签与默认开关等,均以当前仓库实际内容为准;组件在代理内部的 403 判定细节(kube-env 路径匹配方式)位于独立维护的 metadata-proxy 镜像源码中,不在本仓库范围内,因此本文不做展开。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00