
目录
一、问题背景:为什么需要节点级 Pod 隔离?
在共享 Kubernetes 集群中,一个常见的需求是:限制某些用户或应用只能将 Pod 调度到指定的节点上。这种场景在企业内部多团队共享集群、GPU/HPC 资源池隔离、以及对外提供 PaaS 服务时尤为普遍。
传统方案通常有以下几种,但各有短板:
| 方案 | 做法 | 问题 |
|---|---|---|
| Namespace 隔离 | 按团队划分 Namespace | 无法控制 Pod 调度到哪些节点,资源仍可能”越界” |
| NodeSelector / NodeAffinity | 手动在每个 Pod 中添加节点选择器 | 依赖用户自觉,无法强制执行;遗漏即失效 |
| Admission Webhook | 拦截 Pod 创建请求注入约束 | 只能拦截创建,无法过滤查询结果;实现复杂 |
| 多集群 | 为每个租户建独立集群 | 运维成本高,资源利用率低 |
💡 我们需要一种方案:用户可以像正常使用
kubectl一样操作 Pod,但系统在”无感”中强制限制 Pod 只能调度到白名单节点,并且在查询时只返回白名单节点上的 Pod。
二、解决方案:API Aggregator 是什么?
API Aggregator 是一个基于 Kubernetes API Aggregation Layer(API 聚合层)构建的扩展服务。它作为用户与 Kubernetes API Server 之间的”中间人”,拦截所有 Pod 相关的请求,执行节点白名单校验和自动调度注入。
它的核心设计理念是:透明代理 + 选择性拦截。
- 对于 Pod 的增删改查请求 → 拦截并校验节点白名单
- 对于其他资源(Service、ConfigMap 等)→ 透明代理转发到 K8s API Server
这样一来,用户完全不需要改变使用习惯,kubectl get pods、kubectl run 等命令照常使用,只是底层走的不再是原生 API Server,而是经过”过滤”的 Aggregator。
三、架构设计全景
整体架构可以分为三层:用户接入层、Aggregator 拦截层、Kubernetes 底层资源层。
┌─────────────────────────────────────────────────────────┐
│ 用户 / 客户端 │
│ kubectl · Java SDK · curl · CI/CD │
│ (携带 ServiceAccount Token) │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ API Aggregator │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Token校验│→ │路径解析 │→ │操作分发 │→ │节点白名单│ │
│ └─────────┘ └──────────┘ └──────────┘ │ 校验 │ │
│ └──────────┘ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ LIST: 过滤白名单节点 Pod ││
│ │ CREATE: 校验/注入 nodeSelector ││
│ │ DELETE/UPDATE: 校验 Pod 所在节点 ││
│ │ 其他请求: 透明代理 → K8s API ││
│ └─────────────────────────────────────────────────────┘│
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Kubernetes API Server │
│ Node / Pod / Service / ConfigMap ... │
└─────────────────────────────────────────────────────────┘
核心组件说明
| 组件 | 职责 |
|---|---|
| ServiceAccount | 为客户端提供身份认证 Token,所有请求必须携带 |
| RBAC | 最小权限原则,仅授予 Pod 和 Namespace 的必要操作权限 |
| APIService | 将 Aggregator 注册到 Kubernetes API 扩展层,使其成为合法的 API 端点 |
| API Aggregator | 核心服务:拦截请求、过滤列表、校验 nodeName、注入 nodeSelector |
| nodeSelector 标签 | 通过节点标签 node-group=allowed 限制调度范围 |
四、核心功能详解
1. 节点白名单控制
启动时通过 --allowed-nodes 参数指定允许的节点列表。所有 Pod 操作都会与该白名单进行校验,非白名单节点的请求直接返回 403。
2. 智能调度(推荐方式)
用户创建 Pod 时不指定 nodeName,Aggregator 自动注入 nodeSelector: {node-group: allowed}。随后 Kubernetes 调度器会根据资源可用性,在打了 node-group=allowed 标签的白名单节点中选择最优的一个。
💡 这种方式的妙处在于:用户不需要知道有哪些白名单节点,调度器自动处理资源不足时的节点切换。如果某个节点资源不够,Pod 会自动调度到其他白名单节点;只有当所有白名单节点都不够时,Pod 才会保持 Pending。
3. 显式指定节点
用户也可以在 Pod Spec 中显式指定 nodeName,但必须指定白名单中的节点。否则请求被拒绝,并返回当前允许的节点列表。
4. LIST 请求过滤
kubectl get pods 只返回白名单节点上的 Pod。Aggregator 会从 K8s 获取全部 Pod,然后过滤掉非白名单节点的,再返回给用户。用户”看不到”其他节点上的 Pod,实现了视图隔离。
5. 透明代理
对于非 Pod 资源(如 Service、ConfigMap、Deployment 等),Aggregator 不做任何拦截,直接代理转发到 K8s API Server。这意味着用户可以正常操作其他资源,不受影响。
6. 完整的 Pod 生命周期管理
支持 GET(单个/列表)、POST(创建)、PUT(更新)、DELETE(删除)全部操作。每个操作都有相应的白名单校验逻辑。
五、代码实现剖析
项目使用 Go 语言开发,基于 client-go 与 Kubernetes 交互。核心代码不到 350 行,结构清晰。
项目结构
api-aggregator/
├── cmd/aggregator/main.go # 入口:解析 flag、启动服务
├── pkg/apiserver/apiserver.go # 核心:HTTP 路由 + Pod 操作逻辑
├── manifests/ # K8s 部署清单
│ ├── rbac.yaml # ServiceAccount + ClusterRole + Binding
│ ├── deployment.yaml # Deployment(2 副本高可用)
│ ├── service.yaml # Service(NodePort)
│ └── apiservice.yaml # APIService 注册
├── hack/ # 运维脚本
│ ├── generate-certs.sh # 证书生成(10 年有效期)
│ ├── build.sh # 镜像构建
│ ├── deploy.sh # 一键部署
│ └── cleanup.sh # 一键清理
├── Dockerfile # 多阶段构建(支持 amd64/arm64)
├── go.mod # Go 1.21 + k8s.io v0.28.0
└── README.md
请求处理主逻辑
handleRequest 是所有请求的入口。它的处理流程是:
- Token 校验:检查
Authorization: Bearer头 - 路径解析:从 URL 中提取 namespace、resource、podName
- 资源判断:非 Pod 资源直接代理转发
- 方法分发:根据 HTTP Method 调用对应的处理函数
func (s *APIServer) handleRequest(w http.ResponseWriter, r *http.Request) {
// 1. Token 校验
authHeader := r.Header.Get("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
http.Error(w, "Unauthorized: missing token", http.StatusUnauthorized)
return
}
// 2. 路径解析
parts := strings.Split(strings.Trim(path, "/"), "/")
namespace := parts[3]
resource := parts[4] // "pods"
podName := parts[5] // 可能为空
// 3. 非 Pod 资源,透明代理
if resource != "pods" {
s.proxyToK8s(w, r)
return
}
// 4. 方法分发
switch method {
case "GET": // list 或 get
case "POST": // create
case "PUT": // update
case "DELETE": // delete
}
}
CREATE 的核心逻辑:智能调度注入
createPod 是整个系统最关键的函数,它实现了两种调度策略:
func (s *APIServer) createPod(w http.ResponseWriter, r *http.Request, namespace string) {
var pod corev1.Pod
json.NewDecoder(r.Body).Decode(&pod)
// 策略一:用户指定了 nodeName → 校验白名单
if pod.Spec.NodeName != "" {
if !s.AllowedMap[pod.Spec.NodeName] {
http.Error(w, "Node not allowed", http.StatusForbidden)
return
}
// 白名单内,直接创建
s.KubeClient.CoreV1().Pods(namespace).Create(ctx, &pod, ...)
return
}
// 策略二:未指定 nodeName → 注入 nodeSelector,交给调度器
if pod.Spec.NodeSelector == nil {
pod.Spec.NodeSelector = make(map[string]string)
}
pod.Spec.NodeSelector["node-group"] = "allowed"
// 调度器会自动选择打了 node-group=allowed 标签的节点
s.KubeClient.CoreV1().Pods(namespace).Create(ctx, &pod, ...)
}
LIST 过滤逻辑
func (s *APIServer) listPods(w http.ResponseWriter, r *http.Request, namespace string) {
// 获取全部 Pod
pods, _ := s.KubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
// 只保留白名单节点上的 Pod
filtered := &corev1.PodList{Items: []corev1.Pod{}}
for _, pod := range pods.Items {
if s.AllowedMap[pod.Spec.NodeName] {
filtered.Items = append(filtered.Items, pod)
}
}
json.NewEncoder(w).Encode(filtered)
}
透明代理实现
对于非 Pod 请求,proxyToK8s 使用 Pod 内部的 ServiceAccount Token 转发请求到 kubernetes.default.svc:443,实现透明代理:
func (s *APIServer) proxyToK8s(w http.ResponseWriter, r *http.Request) {
token, _ := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
k8sURL := fmt.Sprintf("https://kubernetes.default.svc:443%s", r.URL.Path)
req, _ := http.NewRequestWithContext(r.Context(), r.Method, k8sURL, nil)
req.Header.Set("Authorization", "Bearer "+string(token))
// 复制原始请求头
for key, values := range r.Header {
for _, value := range values {
req.Header.Add(key, value)
}
}
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
// 复制响应
w.WriteHeader(resp.StatusCode)
// ...
}
六、从零部署实战
前置条件
- Kubernetes 集群 v1.19+
kubectl已配置并可访问集群- Docker(用于构建镜像)
- OpenSSL(用于生成证书)
Step 1:修改白名单节点配置
编辑 manifests/deployment.yaml,将 --allowed-nodes 改为你的节点:
args:
- --allowed-nodes=node01,node02
- --v=4
Step 2:构建镜像
chmod +x hack/*.sh
./hack/build.sh
Dockerfile 使用多阶段构建,自动检测 CPU 架构(amd64/arm64),最终镜像基于 Alpine,非常轻量。
Step 3:一键部署
./hack/deploy.sh
部署脚本会自动完成以下所有步骤:
- 生成 TLS 证书(10 年有效期,自动获取节点 IP 和主机名作为 SAN)
- 创建 TLS Secret
- 部署 RBAC(ServiceAccount + ClusterRole + ClusterRoleBinding)
- 部署 Deployment(2 副本高可用)
- 部署 Service(NodePort)
- 等待 Pod 就绪
- 创建 APIService(注册到 K8s API 扩展层)
- 给白名单节点打标签
node-group=allowed - 获取并输出 ServiceAccount Token
Step 4:验证部署
# 查看 Pod 状态
kubectl get pods -n kube-system -l app=api-aggregator
# 查看 APIService 注册状态
kubectl get apiservice v1.node-pod.example.com
# 查看日志
kubectl logs -n kube-system -l app=api-aggregator --tail=20
七、使用方式与示例
获取 Token
SA_SECRET=$(kubectl get serviceaccount api-aggregator \
-n kube-system -o jsonpath='{.secrets[0].name}')
TOKEN=$(kubectl get secret ${SA_SECRET} \
-n kube-system -o jsonpath='{.data.token}' | base64 -d)
echo ${TOKEN}
方式一:不指定节点(智能调度)
kubectl run my-pod \
--image=nginx:latest \
--restart=Never \
-n default \
--token=${TOKEN}
Aggregator 会自动注入 nodeSelector: {node-group: allowed},调度器从白名单节点中选择资源最充足的一个。
方式二:指定白名单节点
kubectl run my-pod \
--image=nginx:latest \
--restart=Never \
-n default \
--overrides='{"spec":{"nodeName":"node01"}}' \
--token=${TOKEN}
方式三:指定非白名单节点(会被拒绝)
kubectl run my-pod \
--image=nginx:latest \
--restart=Never \
--overrides='{"spec":{"nodeName":"node-03"}}' \
--token=${TOKEN}
# 输出:Error from server: Node 'node-03' is not allowed
使用 curl 直接调用 API
# 查询 Pod(只返回白名单节点的 Pod)
curl -k -H "Authorization: Bearer ${TOKEN}" \
https://api-aggregator.kube-system.svc/api/v1/namespaces/default/pods
# 创建 Pod(自动调度)
curl -k -X POST -H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
https://api-aggregator.kube-system.svc/api/v1/namespaces/default/pods \
-d '{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"name": "my-pod"},
"spec": {
"containers": [{"name": "nginx", "image": "nginx:latest"}]
}
}'
# 删除 Pod
curl -k -X DELETE -H "Authorization: Bearer ${TOKEN}" \
https://api-aggregator.kube-system.svc/api/v1/namespaces/default/pods/my-pod
配置 kubectl 专用 Context
可以为用户配置一个专用 context,这样所有 kubectl 命令自动走 Aggregator:
kubectl config set-cluster aggregator-cluster \
--server=https://api-aggregator.kube-system.svc \
--insecure-skip-tls-verify=true
kubectl config set-credentials aggregator-user --token=${TOKEN}
kubectl config set-context aggregator-context \
--cluster=aggregator-cluster \
--user=aggregator-user \
--namespace=default
# 切换到 aggregator context
kubectl config use-context aggregator-context
# 现在 kubectl 只能看到/操作白名单节点的 Pod
kubectl get pods
# 切换回默认 context
kubectl config use-context default
八、安全模型
🔐 安全设计四要素
1. Token 认证 — 所有请求必须携带有效的 ServiceAccount Token,无 Token 或无效 Token 返回 401。
2. 最小权限 RBAC — Aggregator 的 ServiceAccount 仅被授予 Pod 的 CRUD 权限和 Namespace 的只读权限,不触碰其他资源。
3. 节点白名单强制校验 — 所有 Pod 操作都与白名单进行校验,非白名单节点的请求一律 403 拒绝。
4. 长期有效证书 — TLS 证书有效期 10 年(3650 天),自动包含节点 IP 和主机名作为 SAN,避免频繁续期。
九、性能表现
由于 Aggregator 本身只做轻量的拦截和过滤,不做复杂的计算,性能开销极小:
| 指标 | 数值 | 说明 |
|---|---|---|
| API 响应时间 | < 50ms | 大部分时间花在与 K8s API Server 的通信上 |
| 内存使用 | ~128Mi | Go 语言天然内存效率高 |
| CPU 使用 | ~100m | 0.1 核,几乎不消耗 CPU |
| 副本数 | 2 | 高可用部署,支持滚动更新 |
| 镜像大小 | ~20MB | 基于 Alpine,非常轻量 |
十、总结与展望
API Aggregator 通过 Kubernetes 原生的 API 聚合层机制,实现了一种对用户透明、对运维友好的 Pod 节点级隔离方案。它的核心优势在于:
- 零侵入:用户不需要改变任何使用习惯,
kubectl命令照常使用 - 强制执行:白名单校验在服务端强制执行,无法绕过
- 智能调度:自动注入
nodeSelector,由调度器选择最优节点 - 视图隔离:LIST 请求自动过滤,用户只看到白名单节点的 Pod
- 部署简单:一键部署脚本,10 年证书,开箱即用
- 轻量高效:核心代码不到 350 行,资源消耗极低
适用场景
- 企业内部多团队共享 Kubernetes 集群
- GPU/HPC 资源池按节点隔离
- 对外提供 PaaS/CaaS 服务时的租户隔离
- 开发/测试/生产环境的节点级隔离
- 需要限制特定应用只能调度到特定节点的合规场景
后续优化方向
- 支持动态白名单更新(通过 ConfigMap 或 CRD 热加载)
- 增加 Prometheus metrics 暴露,支持监控告警
- 支持基于 Namespace 的差异化白名单
- 集成 OpenTelemetry 实现分布式追踪
- 增加 Webhook 模式作为备选部署方案
💡 如果你也在面对 Kubernetes 多租户 Pod 调度隔离的挑战,不妨试试这个方案。核心代码开源,部署简单,欢迎在自己的集群中尝试。
本文涉及的完整源代码、部署清单和运维脚本已开源。 技术栈:Go 1.21 · Kubernetes v0.28.0 · Alpine Linux · Docker 多阶段构建
点点赞赏,手留余香
共 0 人








请登录后查看评论内容