当前位置:首页 > 技术 > 正文内容

使用 Admission Webhook 自动配置 Pod DNSConfig

访客 技术 2026年6月19日 1

在 Kubernetes 集群中部署 NodeLocal DNSCache 后,还需要配置 Pod 优先使用本地 DNS 缓存服务。本文介绍如何通过自定义 Admission Webhook 实现 Pod DNSConfig 的自动注入。

1.需求背景

部署完成 NodeLocal DNSCache 后,关键的一步是让 Pod 使用 NodeLocal DNSCache 作为首选 DNS 服务器。

实现方式主要有三种:

  • 方案一:修改 kubelet 的 dns nameserver 参数并重启节点。此方式存在业务中断风险,仅建议在测试环境使用。
  • 方案二:创建 Pod 时手动指定 DNSConfig。操作繁琐,不适合大规模使用。
  • 方案三:通过 Webhook 在 Pod 创建时自动注入 DNSConfig。推荐的生产环境方案。

方案一需要修改节点配置,新增节点时也需要重复操作。方案二则需要对每个 Pod 单独配置,工作量巨大。因此生产环境通常采用方案三,通过自定义 Webhook 实现自动化注入。

2.注入规则设计

Webhook 会在 Pod 创建和更新时进行拦截,自动注入 DNSConfig 字段。

作用范围

为了提供更灵活的配置能力,Webhook 仅对特定命名空间下的 Pod 进行注入。通过在命名空间上添加 node-local-dns-injection=enabled 标签来启用自动注入功能。

执行以下命令启用命名空间的自动注入:

kubectl label namespace <namespace-name> node-local-dns-injection=enabled

判定条件

Webhook 根据以下规则判断是否需要对 Pod 进行 DNSConfig 注入:

只有同时满足以下全部条件时,才会进行自动注入:

  • 1)Pod 不属于 kube-system 和 kube-public 命名空间。
  • 2)Pod 所在命名空间包含标签 node-local-dns-injection=enabled。
  • 3)Pod 未被标记为禁用注入(标签 node-local-dns-injection=disabled)。
  • 4)Pod 网络模式与 DNS 策略匹配:hostNetwork 模式下 DNSPolicy 为 ClusterFirstWithHostNet,或非 hostNetwork 模式下 DNSPolicy 为 ClusterFirst。

3.核心实现

源码:lixd/nodelocaldns-admission-webhook

配置定义

首先定义配置结构体,用于指定集群 DNS 和本地 DNS 的地址:

type DNSConfig struct {
	ClusterDNS  string
	LocalDNS    string
}

func LoadConfig(clusterDNS, localDNS string) DNSConfig {
	if len(clusterDNS) == 0 {
		clusterDNS = "10.96.0.10"
	}
	if len(localDNS) == 0 {
		localDNS = "169.254.20.10"
	}
	return DNSConfig{
		ClusterDNS: clusterDNS,
		LocalDNS:   localDNS,
	}
}

启动参数配置:

	flag.StringVar(&clusterDNS, "cluster-dns", "10.96.0.10", "Kubernetes cluster DNS service IP")
	flag.StringVar(&localDNS, "local-dns", "169.254.20.10", "NodeLocal DNS cache service IP")

处理入口

Webhook 的 Handle 方法是请求处理的核心:

func (p *PodMutator) Handle(ctx context.Context, req admission.Request) admission.Response {
	pod := &corev1.Pod{}
	if err := p.Decoder.Decode(req, pod); err != nil {
		return admission.Errored(http.StatusBadRequest, err)
	}
	
	klog.InfoS("Processing pod mutation", 
		"namespace", req.Namespace, 
		"name", pod.Name,
		"operation", req.Operation)

	// 检查是否需要注入
	if !p.shouldMutate(pod) {
		klog.InfoS("Skipping pod - does not meet mutation criteria", 
			"namespace", pod.Namespace, 
			"name", pod.Name)
		return admission.Allowed("mutation not required")
	}

	// 执行注入操作
	p.applyMutation(pod, p.Config)

	mutatedPod, err := json.Marshal(pod)
	if err != nil {
		return admission.Errored(http.StatusInternalServerError, err)
	}
	return admission.PatchResponseFromRaw(req.Object.Raw, mutatedPod)
}

条件判断

shouldMutate 方法根据预设规则判断是否需要注入:

func (p *PodMutator) shouldMutate(pod *corev1.Pod) bool {
	namespace := pod.Namespace
	if len(namespace) == 0 {
		namespace = "default"
	}
	
	// 规则1:跳过系统命名空间
	for _, ns := range systemNamespaces {
		if namespace == ns {
			klog.V(1).InfoS("Skipping system namespace", "namespace", namespace)
			return false
		}
	}

	// 获取命名空间信息
	var ns corev1.Namespace
	if err := p.Client.Get(context.Background(), client.ObjectKey{Name: pod.GetNamespace()}, &ns); err != nil {
		klog.ErrorS(err, "Failed to retrieve namespace", "namespace", pod.Namespace)
		return false
	}

	// 规则2:验证命名空间标签
	if val, exists := ns.Labels[injectionLabel]; !exists || val != "enabled" {
		return false
	}

	// 规则3:检查 Pod 是否禁用注入
	if val, exists := pod.Labels[injectionLabel]; exists && val == "disabled" {
		return false
	}

	// 规则4:验证 DNS 策略与网络模式匹配
	if pod.Spec.HostNetwork && pod.Spec.DNSPolicy != corev1.DNSClusterFirstWithHostNet {
		return false
	}

	if !pod.Spec.HostNetwork && pod.Spec.DNSPolicy != corev1.DNSClusterFirst {
		return false
	}

	return true
}

注入逻辑

applyMutation 方法负责修改 Pod 的 DNSConfig:

func (p *PodMutator) applyMutation(pod *corev1.Pod, cfg DNSConfig) {
	ns := pod.Namespace
	if len(ns) == 0 {
		ns = "default"
	}
	pod.Spec.DNSPolicy, pod.Spec.DNSConfig = p.buildDNSConfig(ns, cfg)
}

func (p *PodMutator) buildDNSConfig(namespace string, cfg DNSConfig) (corev1.DNSPolicy, *corev1.PodDNSConfig) {
	serviceName := fmt.Sprintf("%s.svc.cluster.local", namespace)
	return "None", &corev1.PodDNSConfig{
		Nameservers: []string{cfg.LocalDNS, cfg.ClusterDNS},
		Searches:    []string{serviceName, "svc.cluster.local", "cluster.local"},
		Options: []corev1.PodDNSConfigOption{
			{
				Name:  "ndots",
				Value: strPtr("3"),
			},
			{
				Name:  "attempts",
				Value: strPtr("2"),
			},
			{
				Name:  "timeout",
				Value: strPtr("1"),
			},
		},
	}
}

func strPtr(s string) *string {
	return &s
}

整个实现逻辑清晰:Webhook 接收 Pod 创建/更新请求后,首先根据规则判断是否需要注入,满足条件则根据配置生成 DNSConfig 并返回补丁。

4.部署步骤

部署包含两部分内容:Webhook 服务和 Kubernetes 准入配置。

服务部署

需要部署以下组件:

  • cert-manager:自动管理 Webhook 证书
  • RBAC:授权 Webhook 访问 Pod 和 Namespace 资源
  • Deployment:运行 Webhook 服务

项目 /deploy 目录下提供了完整的部署清单。

部署过程:

  • 使用 cert-manager 签发 TLS 证书
  • 配置 RBAC 权限
  • 部署 Webhook Deployment 和 Service

默认配置参数可通过启动参数修改:

  • -cluster-dns:集群 DNS 地址,默认为 10.96.0.10
  • -local-dns:NodeLocal DNS 地址,默认为 169.254.20.10

部署清单 webhook-deploy.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodelocaldns-webhook
  namespace: kube-system
  labels:
    app: nodelocaldns
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nodelocaldns
  template:
    metadata:
      labels:
        app: nodelocaldns
    spec:
      serviceAccountName: nodelocaldns-webhook
      containers:
        - name: nodelocaldns-webhook
          image: lixd96/nodelocaldns-admission-webhook:v0.0.1
          imagePullPolicy: IfNotPresent
          command:
            - /manager
          args:
            - "-cluster-dns=10.96.0.10"
            - "-local-dns=169.254.20.10"
          volumeMounts:
            - name: webhook-certs
              mountPath: /tmp/k8s-webhook-server/serving-certs
              readOnly: true
      volumes:
        - name: webhook-certs
          secret:
            secretName: nodelocaldns-webhook
---
apiVersion: v1
kind: Service
metadata:
  name: nodelocaldns-webhook
  namespace: kube-system
  labels:
    app: nodelocaldns
spec:
  ports:
    - port: 443
      targetPort: 9443
  selector:
    app: nodelocaldns

执行部署:

cd deploy
kubectl apply -f cert-manager
kubectl apply -f webhook-deploy.yaml
kubectl apply -f webhook-rbac.yaml

准入配置

MutatingWebhookConfiguration 配置清单:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: mutating-webhook-configuration
  annotations:
    cert-manager.io/inject-ca-from: kube-system/nodelocaldns-webhook
webhooks:
  - admissionReviewVersions:
      - v1
    clientConfig:
      service:
        name: nodelocaldns-webhook
        namespace: kube-system
        path: /mutate-v1-pod
    failurePolicy: Fail
    name: nodelocaldns-webhook.kube-system.svc
    namespaceSelector:
      matchLabels:
        node-local-dns-injection: enabled
    rules:
      - apiGroups:
          - ""
        apiVersions:
          - v1
        operations:
          - CREATE
          - UPDATE
        resources:
          - pods
    sideEffects: None

关键配置说明:

  • cert-manager.io/inject-ca-from:自动注入 CA 证书
  • namespaceSelector:限制只对带标签的命名空间生效
  • rules:仅处理 Pod 的创建和更新操作

部署准入配置:

cd deploy 
kubectl apply -f webhook-config.yaml

5.验证测试

首先为命名空间启用注入:

kubectl label namespace default node-local-dns-injection=enabled

创建测试 Pod:

kubectl run busybox --image=busybox --restart=Never --namespace=default --command -- sleep infinity

查看 Pod 完整配置:

kubectl get pod busybox -o yaml

关键配置已注入:

spec:
  dnsConfig:
    nameservers:
    - 169.254.20.10
    options:
    - name: ndots
      value: "2"
    searches:
    - default.svc.cluster.local
    - svc.cluster.local
    - cluster.local
  dnsPolicy: None

验证未启用注入的命名空间:

kubectl create namespace myns
kubectl run busybox --image=busybox --restart=Never --namespace=myns --command -- sleep infinity
kubectl get pod busybox -o yaml -n myns

该 Pod 的 dnsConfig 未被修改,说明注入逻辑工作正常。

验证 DNS 解析功能:

kubectl run test-pod --image=busybox --restart=Never --namespace=default
kubectl exec -it test-pod -- nslookup kubernetes.default.svc.cluster.local

解析成功,Nameserver 显示为 169.254.20.10,证明 NodeLocalDNS 正常工作。

6.总结

通过自定义 Admission Webhook,实现了 Pod DNSConfig 的自动注入功能。Webhook 根据命名空间标签和 Pod 标签智能判断是否需要注入,满足条件的 Pod 会自动获得以 NodeLocalDNS 为首选 DNS 服务器的配置。整个过程自动化完成,无需手动修改 Pod YAML。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。