实战:用 Kubernetes Webhook 实现 Pod 节点调度白名单

2026年8月11日xiaohei0 条互动约 19 分钟

实战:用 Kubernetes Webhook 实现 Pod 节点调度白名单

AI摘要
AI摘要 此内容由AI根据正文内容自动生成
本文介绍了一种基于 Kubernetes Admission Webhook 的 Pod 节点调度强制限制方案。为满足生产环境中 GPU 隔离、高可用及安全合规等需求,该方案通过准入控制机制实现了节点白名单校验、自动注入节点选择器以及命名空间级别的精细化管控。文章详细讲解了架构设计、一键部署流程、多语言调用示例及测试验证方法,为 K8s 集群提供了安全可靠的节点调度约束实践。
标签:KubernetesAdmission WebhookPod调度

一、背景介绍

在 Kubernetes 集群中,默认情况下 Pod 可以被调度到任何节点上。但在实际生产环境中,我们经常有这样的需求:

  • GPU 节点限制:只有特定的应用才能使用 GPU 节点
  • 高可用隔离:关键应用只运行在特定节点
  • 资源隔离:不同团队使用不同的节点池
  • 安全合规:某些节点只允许特定权限的 Pod 运行

虽然 Kubernetes 提供了 nodeSelector 和 nodeAffinity 可以实现节点选择,但这些配置是由用户主动设置的,无法强制约束。如果用户不指定或指定了错误的节点,Pod 仍然可以被调度到不该去的节点。

解决方案:通过 Admission Webhook 强制限制 Pod 只能调度到白名单节点。

二、什么是 Admission Webhook?

Admission Webhook 是 Kubernetes 提供的准入控制机制,可以在 Pod 创建、更新、删除时进行拦截和校验。

两种 Webhook 类型:

类型作用说明
ValidatingWebhook验证请求检查 Pod 配置是否合规,不合规则拒绝
MutatingWebhook修改请求在 Pod 创建时自动注入配置

工作流程:

图片[1]-实战:用 Kubernetes Webhook 实现 Pod 节点调度白名单-UCBK · 栈域

三、功能特性

本项目实现了一套完整的 Webhook 方案,具备以下功能:

1.节点白名单控制

只允许 Pod 在指定的节点上运行,白名单节点通过环境变量配置:

ALLOWED_NODES: "node-01,node-02,node-03"

2.自动注入节点选择器

用户创建 Pod 时可以不指定节点,Webhook 会自动:

  • 选择白名单中的一个节点
  • 注入 nodeSelector: {“allowed-node”: “true”}

3.强制指定节点
用户也可以显式指定 nodeName,Webhook 会校验是否在白名单中。

4.命名空间级别控制
通过 namespaceSelector 控制 Webhook 只在特定命名空间生效。

5.完整的 RBAC 权限控制
通过 ServiceAccount + RBAC 精细化控制谁能创建 Pod。

四、架构设计

图片[2]-实战:用 Kubernetes Webhook 实现 Pod 节点调度白名单-UCBK · 栈域

五、快速部署

1.下载项目

git clone https://github.com/MoeXiaoHei/webhook-node-validator.git
cd webhook-node-validator

2. 修改配置

编辑 5-webhook-deployment.yaml,设置白名单节点:

env:
- name: ALLOWED_NODES
  value: "node01,node01,node01"  # 改为你的节点

3.一键部署

# 给脚本添加执行权限
chmod +x deploy.sh cleanup.sh generate-certs.sh
# 执行部署
./deploy.sh

4.验证部署

# 查看 Webhook Pod 状态
kubectl get pods -n my-namespace -l app=node-validation-webhook
# 输出示例:
# NAME                                      READY   STATUS    RESTARTS   AGE
# node-validation-webhook-cb47f8996-q54jp   1/1     Running   0          5m
# node-validation-webhook-cb47f8996-x6rdl   1/1     Running   0          5m
# 查看日志
kubectl logs -n my-namespace -l app=node-validation-webhook

六、使用方式

1.获取 Token

# 获取 ServiceAccount Token
SA_SECRET=$(kubectl get serviceaccount multi-node-operator-sa -n my-namespace -o jsonpath='{.secrets[0].name}')
TOKEN=$(kubectl get secret ${SA_SECRET} -n my-namespace -o jsonpath='{.data.token}' | base64 -d)
echo ${TOKEN}

2.创建 Pod(指定节点)

# 在白名单节点创建 Pod
kubectl run my-pod \
  --image=nginx:latest \
  --restart=Never \
  -n my-namespace \
  --overrides='{"spec":{"nodeName":"node01"}}' \
  --token=${TOKEN}

3.创建 Pod(不指定节点)

4.使用 Java 代码调用

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
public class K8sClient {
    private static final String API_URL = "https://kubernetes.default.svc:6443";
    private static final String TOKEN = "eyJhbGciOiJSUzI1NiIsImtpZCI6...";
    private final HttpClient httpClient = HttpClient.newHttpClient();
    public String createPod(String namespace, String podName, String nodeName) throws Exception {
        String url = API_URL + "/api/v1/namespaces/" + namespace + "/pods";
        String podJson = String.format(
            "{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"name\":\"%s\"},\"spec\":{\"nodeName\":\"%s\",\"containers\":[{\"name\":\"nginx\",\"image\":\"nginx:latest\"}]}}",
            podName, nodeName
        );
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("Authorization", "Bearer " + TOKEN)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(podJson))
            .build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        return response.body();
    }
}

5.使用 Fabric8 Kubernetes Client

import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
public class K8sClient {
    private final KubernetesClient client;
    public K8sClient(String token) {
        Config config = new ConfigBuilder()
            .withMasterUrl("https://kubernetes.default.svc:6443")
            .withOauthToken(token)
            .withTrustCerts(true)
            .build();
        this.client = new KubernetesClientBuilder().withConfig(config).build();
    }
    public Pod createPod(String namespace, String name, String nodeName) {
        Pod pod = new PodBuilder()
            .withNewMetadata().withName(name).endMetadata()
            .withNewSpec()
                .withNodeName(nodeName)
                .addNewContainer().withName("nginx").withImage("nginx:latest").endContainer()
            .endSpec()
            .build();
        return client.pods().inNamespace(namespace).create(pod);
    }
}

6.使用 curl 调用

# 创建 Pod
curl -k -X POST \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  https://kubernetes.default.svc:6443/api/v1/namespaces/my-namespace/pods \
  -d '{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {"name": "my-pod"},
    "spec": {
      "nodeName": "node01",
      "containers": [{"name": "nginx", "image": "nginx:latest"}]
    }
  }'

七、测试验证

测试 1:在白名单节点创建 Pod(成功)

kubectl run test-allowed \
  --image=nginx:latest \
  --restart=Never \
  -n my-namespace \
  --overrides='{"spec":{"nodeName":"node01"}}' \
  --token=${TOKEN}

# 查看 Pod
kubectl get pod test-allowed -n my-namespace -o wide
# NAME           READY   STATUS    RESTARTS   AGE   IP            NODE
# test-allowed   1/1     Running   0          10s   10.244.1.2    node01

测试 2:在非白名单节点创建 Pod(失败)

kubectl run test-denied \
  --image=nginx:latest \
  --restart=Never \
  -n my-namespace \
  --overrides='{"spec":{"nodeName":"node-04"}}' \
  --token=${TOKEN}
# 预期输出:
# Error from server: admission webhook "node-validation.my-namespace.svc" denied the request: Node 'node-03' is not allowed. Allowed nodes: node01, node02, node03

测试 3:不指定节点(自动调度)

kubectl run test-auto \
  --image=nginx:latest \
  --restart=Never \
  -n my-namespace \
  --token=${TOKEN}
# 查看 Pod 自动调度到哪个节点
kubectl get pod test-auto -n my-namespace -o wide

八、命名空间级别控制

通过 namespaceSelector 控制 Webhook 只在特定命名空间生效:

7-validating-webhook.yaml

# 7-validating-webhook.yaml
namespaceSelector:
  matchLabels:
    webhook-enabled: "true"  # 只有打了这个标签的命名空间才生效
启用命名空间
kubectl label namespace my-namespace webhook-enabled=true
禁用命名空间
kubectl label namespace my-namespace webhook-enabled-
查看命名空间标签
kubectl get namespaces --show-labels

九、清理

# 一键清理所有资源
./cleanup.sh

十、项目文件说明

文件作用
1-serviceaccount.yamlServiceAccount,用于生成 Token
2-rbac.yamlRBAC 权限配置
3-webhook-code-configmap.yamlWebhook Python 代码
5-webhook-deployment.yamlWebhook Deployment
6-webhook-service.yamlWebhook Service
7-validating-webhook.yamlValidatingWebhookConfiguration
9-mutating-webhook.yamlMutatingWebhookConfiguration
deploy.sh一键部署脚本
cleanup.sh一键清理脚本

十一、总结

通过 Admission Webhook 方案,我们可以:

  1. 强制限制 Pod 只能调度到白名单节点
  2. 自动注入 nodeSelector,用户无需手动指定
  3. 灵活控制 命名空间级别的生效范围
  4. 最小改动 用户代码无需修改,只需使用特定 Token

适用场景

  • 多租户集群资源隔离
  • GPU 节点专机专用
  • 高可用应用节点限制
  • 安全合规要求

🎉 项目地址: https://github.com/MoeXiaoHei/webhook-node-validator.git

点点赞赏,手留余香

0

公众号
© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容