本文主要分享如何使用 基于 Admission Webhook 实现自动修改 Pod DNSConfig,使其优先使用 NodeLocalDNS 。

1.背景

上一篇部署好 NodeLocal DNSCache,但是还差了很重要的一步,配置 pod 使用 NodeLocal DNSCache 作为优先的 DNS 服务器。

有以下几种方式:

  • 方式一:修改 kubelet 中的 dns nameserver 参数,并重启节点 kubelet。存在业务中断风险,不推荐使用此方式

    • 测试时可以用这个方式,比较简单
  • 方式二:创建 Pod 时手动指定 DNSConfig,比较麻烦,不推荐。
  • 方式三:借助 DNSConfig 动态注入控制器在 Pod 创建时配置 DNSConfig 自动注入,推荐使用此方式。
    • 需要自己实现一个 webhook,相当于把方式二自动化了

第一种方式存在业务中断风险,而且后续新增节点时也需要修改 kubelet 配置,比较麻烦。

而第二种方式则每个创建的 Pod 都需要手动指定 DNSConfig 就更繁琐了。

因此一般是推荐使用第三种方式,实现一个 Webhook,由该 Webhook 来自动修改 Pod 的 DNSConfig。

2. 自动注入规则

Admission Webhook 用于自动注入 DNSConfig 到新建的 Pod 中,避免您手工配置 Pod YAML进行注入。

注入范围

为了使应用更灵活,我们指定,只对携带node-local-dns-injection=enabled label 的命名空间中新建 Pod 的进行注入。

可以通过以下命令给命名空间打上Label标签:

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

注入规则

Webhook 则是在所有 Pod 创建、更新前都会进行检测,如果 Pod 所在 Namespace 满足条件,或者 Pod 也满足条件则自动注入 DNSConfig,将 NodeLocalDNS 作为 Pod 的优先 DNS 服务。

具体规则如下:

Pod 在同时满足以下条件时,才会自动注入 DNS 缓存。如果您的 Pod 容器未注入 DNS 缓存服务器的 IP 地址,请检查 Pod 是否未满足以下条件。

  • 1)新建 Pod 不位于 kube-system 和 kube-public 命名空间。
  • 2)新建 Pod 所在命名空间的 Labels 标签包含 node-local-dns-injection=enabled。
  • 3)新建 Pod 没有被打上禁用 DNS 注入 node-local-dns-injection=disabled 标签。
  • 4)新建 Pod 的网络为 hostNetwork 且 DNSPolicy 为 ClusterFirstWithHostNet,或 Pod 为非 hostNetwork 且 DNSPolicy 为 ClusterFirst。

3. Admission Webhook 实现

源码:lixd/nodelocaldns-admission-webhook

配置文件

我们可以通过配置文件来执行 KubeDNS 地址和 NodeLocalDNS 地址,也提供了默认值。

const (
DefaultKubeDNS = "10.96.0.10"
DefaultLocalDNS = "169.254.20.10"
) func NewDNSConfig(kubedns, localdns string) Config {
if kubedns == "" {
kubedns = DefaultKubeDNS
}
if localdns == "" {
localdns = DefaultLocalDNS
}
return Config{
KubeDNS: kubedns,
LocalDNS: localdns,
}
}

启动服务时可以指定

	flag.StringVar(&kubedns, "kube-dns", "10.96.0.10", "The service ip of kube dns.")
flag.StringVar(&localdns, "local-dns", "169.254.20.10", "The virtual ip of node local dns.")

注入 DNSConfig

Webhook Handle 方法中就是核心逻辑。

func (a *PodAnnotator) Handle(ctx context.Context, req admission.Request) admission.Response {
pod := &corev1.Pod{}
err := a.Decoder.Decode(req, pod)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
klog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v",
req.Kind, req.Namespace, req.Name, pod.Name, req.UID, req.Operation, req.UserInfo) // determine whether to perform mutation
if !a.NeedMutation(pod) {
klog.Infof("Skipping mutation for %s/%s due to policy check", pod.Namespace, pod.Name)
return admission.Allowed("not need mutation,skip")
} // mutate the fields in pod
mutation(pod, a.Config) marshaledPod, err := json.Marshal(pod)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPod)
}

首先通过 NeedMutation 判断是否满足条件,如果不需要注入则跳过

如果需要则执行 mutation 方法修改 Pod 的 DNSConfig 字段。

NeedMutation

这里就是按照之前提到的注入规则进行判定

// NeedMutation Check whether the target resoured need to be mutated
func (a *PodAnnotator) NeedMutation(pod *corev1.Pod) bool {
if pod.Namespace == "" {
pod.Namespace = "default"
}
/*
Pod will automatically inject DNS cache when all of the following conditions are met:
1. The newly created Pod is not in the kube-system and kube-public namespaces.
2. The Labels of the namespace where the new Pod is located contain node-local-dns-injection=enabled.
3. The newly created Pod is not labeled with the disabled DNS injection node-local-dns-injection=disabled label.
4. The network of the newly created Pod is hostNetwork and DNSPolicy is ClusterFirstWithHostNet, or the Pod is non-hostNetwork and DNSPolicy is ClusterFirst.
*/
//1. The newly created Pod is not in the kube-system and kube-public namespaces.
for _, namespace := range ignoredNamespaces {
if pod.Namespace == namespace {
klog.V(1).Infof("Skip mutation for %v for it's in special namespace: %v", pod.Name, pod.Namespace)
return false
}
} // Fetch the namespace where the Pod is located.
var ns corev1.Namespace
err := a.Client.Get(context.Background(), client.ObjectKey{Name: pod.GetNamespace()}, &ns)
if err != nil {
klog.V(1).ErrorS(err, "Failed to fetch namespace: %v", pod.Namespace)
return false
} //2. The Labels of the namespace where the new Pod is located contain node-local-dns-injection=enabled.
if v, ok := ns.Labels[NodeLocalDNSInjection]; !ok || v != "enabled" {
return false
} //3. The newly created Pod is not labeled with the disabled DNS injection node-local-dns-injection=disabled label.
if v, ok := pod.Labels[NodeLocalDNSInjection]; ok && v == "disabled" {
return false
} //4. The network of the newly created Pod is hostNetwork and DNSPolicy is ClusterFirstWithHostNet, or the Pod is non-hostNetwork and DNSPolicy is ClusterFirst.
// The network of the Pod is hostNetwork, so DNSPolicy should be ClusterFirstWithHostNet.
if pod.Spec.HostNetwork && pod.Spec.DNSPolicy != corev1.DNSClusterFirstWithHostNet {
return false
} // The network of the Pod is not hostNetwork, so DNSPolicy should be ClusterFirst.
if !pod.Spec.HostNetwork && pod.Spec.DNSPolicy != corev1.DNSClusterFirst {
return false
} // If all conditions are met, return true.
return true
}

mutation

mutation 则是根据配置文件组装好 DNSConfig 并注入到 Pod。

func mutation(pod *corev1.Pod, conf Config) {
ns := pod.Namespace
if ns == "" {
ns = "default"
}
pod.Spec.DNSPolicy, pod.Spec.DNSConfig = loadCustomDnsConfig(ns, conf)
} func loadCustomDnsConfig(namespace string, config Config) (corev1.DNSPolicy, *corev1.PodDNSConfig) {
nsSvc := fmt.Sprintf("%s.svc.cluster.local", namespace)
return "None", &corev1.PodDNSConfig{
Nameservers: []string{config.LocalDNS, config.KubeDNS},
Searches: []string{nsSvc, "svc.cluster.local", "cluster.local"},
Options: []corev1.PodDNSConfigOption{
{
Name: "ndots",
Value: StringPtr("3"),
},
{
Name: "attempts",
Value: StringPtr("2"),
},
{
Name: "timeout",
Value: StringPtr("1"),
},
},
}
}

至此,核心逻辑就结束了,还是比较简单的,对于每个 Pod 创建、更新请求,Webhook 中都判断该 Pod 是否需要注入,不满足条件则直接跳过,满足条件则根据配置生成 DNSConfig 并注入到 Pod 中。

4. 部署

包含两部分:

  • 1)Webhook 本身部署
  • 2)K8s 中增加 Webhook 配置

Webhook 部署

需要部署以下几部分内容:

  • Cert-manager : 由于 Webhook 需要配置证书,建议使用 cert-manager 来自动注入,减少手动操作。
  • RBAC:Webhook 需要查询 Pod、Namespace 等信息,因此需要授权
  • Deploy:Webhook 本身以 Deploy 方式部署。

具体文件都在 /deploy 目录下,直接使用即可。

在 deploy 目录提供了部署相关 yaml,apply 即可。

  • 1)部署 cert-manager 用于管理证书
  • 2)创建 Issuer、Certificate 对象,让 cert-manager 签发证书并存放到 Secret
  • 3)创建 rbac 并部署 Webhook, 挂载 2 中的 Secret 到容器中以开启 TLS
    • 可以修改启动命令中的 -kube-dns 和 -local-dns 参数来调整 KubeDNS 和 NodeLocalDNS 地址,默认为 10.96.0.10 和 169.254.20.10。

webhook-deploy.yaml 如下,就是一个普通的 Deployment:

镜像已经推送到了 Dockerhub,大家可以直接使用

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 # 提供查询 namespace 信息的权限
containers:
- name: nodelocaldns-webhook
image: lixd96/nodelocaldns-admission-webhook:v0.0.1
imagePullPolicy: IfNotPresent
command:
- /manager
args:
- "-kube-dns=10.96.0.10"
- "-local-dns=169.254.20.10"
volumeMounts:
- name: webhook-certs
mountPath: /tmp/k8s-webhook-server/serving-certs # Webhook 证书默认路径
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
# 部署 CertManager 以及签发证书
kubectl apply -f cert-manager # 部署 Webhook
kubectl apply -f webhook-deploy.yaml
kubectl apply -f webhook-rbac.yaml

MutatingWebhookConfiguration

yaml 大概是这样的:

---
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:
#caBundle: ""
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 annotation 让 CertManager 自动注入 CA 证书。

  annotations:
cert-manager.io/inject-ca-from: kube-system/nodelocaldns-webhook

限制生效范围

    namespaceSelector: # 限制生效范围
matchLabels:
node-local-dns-injection: enabled

只关心 Pod 的 Create、Update 事件:

    rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- pods

也是直接 apply 即可

cd deploy
kubectl apply -f webhook-config.yaml

5. 测试

首先给 default namespace 打上 node-local-dns-injection=enabled label。

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

创建一个 Pod,然后查看 yaml 看看 dnsConfig 是否被修改了。

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

查看一下完整 Yaml

[root@webhook ~]# k get po busybox -oyaml
apiVersion: v1
kind: Pod
metadata:
annotations:
cni.projectcalico.org/containerID: 2a4caca308b031f872c47ef334cf7e940d74646a2f0a8893c7786508d30ed488
cni.projectcalico.org/podIP: 172.25.233.215/32
cni.projectcalico.org/podIPs: 172.25.233.215/32
creationTimestamp: "2024-02-05T10:43:16Z"
labels:
run: nginx-pod
name: nginx-pod
namespace: default
resourceVersion: "19341"
uid: 2b107b50-e85c-462f-8919-a0c01114bae6
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx-pod
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-4wf2n
readOnly: true
dnsConfig:
nameservers:
- 169.254.20.10
options:
- name: ndots
value: "2"
searches:
- default.svc.cluster.local
- svc.cluster.local
- cluster.local
dnsPolicy: None

Dns 部分如下:

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

可以看到,已经注入了我们的 NodeLocalDNS 了。

然后往没有打 Label 的命名空间创建 Pod

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

查看 DNSConfig 是否被修改

[root@webhook ~]# kubectl -n myns get pod nginx-pod -oyaml
apiVersion: v1
kind: Pod
metadata:
annotations:
cni.projectcalico.org/containerID: 93a545988d7c7bbb88f0bc0e745226cd9e684bd63b78754dadd738861ed34512
cni.projectcalico.org/podIP: 172.25.233.218/32
cni.projectcalico.org/podIPs: 172.25.233.218/32
creationTimestamp: "2024-02-06T01:22:36Z"
labels:
run: nginx-pod
name: nginx-pod
namespace: myns
resourceVersion: "116195"
uid: 1f64a831-7470-49d5-b28e-1cd231ef5d8f
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx-pod
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-shk68
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true

可以看到,并没有,说明我们的逻辑是没问题的,只会对打了 Label 的命名空间中的 Pod 进行注入。

最后测试能否正常解析

测试一下注入 DNSConfig 之后能否正常解析 DNS

kubectl run busybox-pod --image=busybox --restart=Never --namespace=default

进入 Pod 并测试解析 Service 记录

[root@webhook ~]# k exec -it busybox-pod -- nslookup nodelocaldns-webhook.kube-system.svc.cluster.local
Server: 169.254.20.10
Address: 169.254.20.10:53 Name: nodelocaldns-webhook.kube-system.svc.cluster.local
Address: 10.105.137.213
[root@webhook ~]# kk get svc nodelocaldns-webhook
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nodelocaldns-webhook ClusterIP 10.105.137.213 <none> 443/TCP 14h

可以看到,Nameserver 是 169.254.20.10,也就是我们的 NodeLocalDNS,然后能拿到正确的 IP,说明我们的 NodeLocalDNS 是没问题的。

6. 小结

本文主要分析了如何通过自定义一个 Admission Webhook 来自动化的修改 Pod 的 DNSConfig,使其优先使用 NodeLocalDNS。

基于 Admission Webhook 实现 Pod DNSConfig 自动注入的更多相关文章

  1. Istio技术与实践03:最佳实践之sidecar自动注入

    Istio通过对serviceMesh中的每个pod注入sidecar,来实现无侵入式的服务治理能力.其中,sidecar的注入是其能力实现的重要一环(本文主要介绍在kubernetes集群中的注入方 ...

  2. istio实现自动sidecar自动注入(k8s1.13.3+istio1.1.1)

    一.自动注入的前提条件 自动注入功能需要kubernetes 1.9或更高版本: kubernetes环境需支持MutatingAdmissionWebhook: 二.在namespace中设置自动注 ...

  3. 基于webhook方案的Git自动部署方案

    之前已经用Git实现了自己博客的提交自动部署,并自动提交到GitHub和coding以备不时之需.平时项目代码都托管在Coding或者GitHub上,也已经用上了coding提供的webhook功能, ...

  4. 1.深入Istio:Sidecar自动注入如何实现的?

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的Istio源码是 release 1.5. 这篇文章打算讲一下sidecar, ...

  5. istio sidecar自动注入过程分析

    目录 istio sidecar自动注入过程分析 sidecar自动注入检查 检查kube-apiserver 检查sidecar-injector的configmap 检查namespace标签 s ...

  6. Kubernetes-Istio之Sidecar自动注入

    前提: (官方提供) 1):确认使用的是Kubernetes服务器的受支持版本( 1.13.1.14.1.15):kubectl (官方提供,应该是1.13版本以上,我的是1.16版本) kubect ...

  7. 使用 Admission Webhook 机制实现多集群资源配额控制

    1 要解决的问题 集群分配给多个用户使用时,需要使用配额以限制用户的资源使用,包括 CPU 核数.内存大小.GPU 卡数等,以防止资源被某些用户耗尽,造成不公平的资源分配. 大多数情况下,集群原生的 ...

  8. 小白日记46:kali渗透测试之Web渗透-SqlMap自动注入(四)-sqlmap参数详解- Enumeration,Brute force,UDF injection,File system,OS,Windows Registry,General,Miscellaneous

    sqlmap自动注入 Enumeration[数据枚举] --privileges -U username[CU 当前账号] -D dvwa -T users -C user --columns  [ ...

  9. 小白日记45:kali渗透测试之Web渗透-SqlMap自动注入(三)-sqlmap参数详解-Optimization,Injection,Detection,Techniques,Fingerprint

    sqlmap自动注入 Optimization [优化性能参数,可提高效率] -o:指定前三个参数(--predict-output.--keep-alive.--null-connection) - ...

  10. 小白日记44:kali渗透测试之Web渗透-SqlMap自动注入(二)-sqlmap参数详解REQUEST

    Sqlmap自动注入(二) Request ################################################### #inurl:.php?id= 1. 数据段:--d ...

随机推荐

  1. myBatis插入操作获取不到返回的自增id问题

    myBatis插入操作后想返回自增 id 有多种方式 其中一种使用率较高的就是: 在<insert></insert> 标签中添加 useGeneratedKeys 和 key ...

  2. C语言数据类型和变量

    目录 1.数据类型介绍 1.1字符型 1.2整形 1.3浮点型 1.4布尔类型 1.5各种数据类型长度 1.5.1sizeof操作符 1.5.2数据类型长度 1.5.3 sizeof中表达式不计算 2 ...

  3. CSS动画(波光粼粼登录页面)

    1.整体效果 https://mmbiz.qpic.cn/sz_mmbiz_gif/EGZdlrTDJa4AbemkU3vLRIDzTIgPHSjicia97wfvMVAhqZL4lsGbQQCbsV ...

  4. Docker Compose容器编排--项目五

    一.Docker Compose概念 Docker Compose (可简称Compose)是一个定义与运行复杂应用程序的 Docker 工具,是 Docker 官方 编排(Orchestration ...

  5. SSIS以yyyyMMdd的形式获取当前系统时间

    公式: (DT_WSTR,4)YEAR(GETDATE()) + RIGHT("0" + (DT_WSTR,2)MONTH(GETDATE()),2) + RIGHT(" ...

  6. 在react中使用阿里图标库

    参考教程:https://blog.csdn.net/qq_41977441/article/details/110352463 阿里图标库:https://www.iconfont.cn/ 成果展示 ...

  7. 题解:洛谷P3745 期末考试(整数三分)

    题解:洛谷P3745 期末考试(整数三分) 题目传送门 题目大意:给出 \(n\) 个同学期望出成绩的时间限制 \(a_i\) 和 \(m\) 个学科公布成绩的初始时间 \(t_i\) ,1个同学每多 ...

  8. Linux环境下非GUI制作图形界面方法

    Linux环境下非GUI制作图形界面方法 如题,即就是仅仅使用ANSI转义字符实现Linux环境的页面效果,如字体颜色.背景颜色.高亮.固定位置光标.将光标放到指定位置.隐藏字符串等等. 具体实现方法 ...

  9. 快速上手 KSQL:轻松与数据库交互的利器

    上次我们通过 Docker 安装了 KingbaseES 数据库,今天我们将开始学习并快速上手使用 KSQL.简单来说,KSQL 本质上是一个客户端工具,用于与数据库进行交互.启动后,我们可以像使用普 ...

  10. 负载测试工具之Fortio

    github: github.com/fortio/fortio 日常开发中通常需要知道系统能承受的最大负载,不满足当前需求时对系统软硬件进行相应的优化或升级.今天推荐的工具 Fortio 就是用来测 ...