Karmada 结合 coreDNS 插件实现跨集群统一域名访问
本文分享自华为云社区《Karmada 结合 coreDNS 插件实现跨集群统一域名访问》,作者:云容器大未来 。
在多云与混合云越来越成为企业标配的今天,服务的部署和访问往往不在一个 K8s集群中。如何做到服务访问与集群无关,成为了各个云服务提供商必须要面对的问题。本文基于Karmada v1.6.1版本,探索使用一致域名跨集群访问服务的方法,来解决该问题。
一、实践官方例子
1. 部署业务
apiVersion: apps/v1 kind: Deployment metadata: name: serve spec: replicas: 2 selector: matchLabels: app: serve template: metadata: labels: app: serve spec: containers: - name: serve image: jeremyot/serve:0a40de8 args: - "--message='hello from cluster member1 (Node: {{env \"NODE_NAME\"}} Pod: {{env \"POD_NAME\"}} Address: {{addr}})'" env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name dnsPolicy: ClusterFirst # 优先使用集群的coredns来解析 --- apiVersion: v1 kind: Service metadata: name: serve spec: ports: - port: 80 targetPort: 8080 selector: app: serve --- apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: mcs-workload spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: serve - apiVersion: v1 kind: Service name: serve placement: clusterAffinity: clusterNames: - member1
2. 创建 ServiceExport 与 ServiceImport 的 CPP规则
需要在控制面创建 ServiceExport 与 ServiceImport 两个 CRD 前创建其在集群的传播规则 。本例中通过 ClusterPropagationPolicy 将这两个CRD 安装到member1 和 member2 中去。该步骤的 yaml 如下:
# propagate ServiceExport CRD apiVersion: policy.karmada.io/v1alpha1 kind: ClusterPropagationPolicy metadata: name: serviceexport-policy spec: resourceSelectors: - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition name: serviceexports.multicluster.x-k8s.io placement: clusterAffinity: clusterNames: - member1 - member2 --- # propagate ServiceImport CRD apiVersion: policy.karmada.io/v1alpha1 kind: ClusterPropagationPolicy metadata: name: serviceimport-policy spec: resourceSelectors: - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition name: serviceimports.multicluster.x-k8s.io placement: clusterAffinity: clusterNames: - member1 - member2
3. 创建从成员集群导出service
apiVersion: multicluster.x-k8s.io/v1alpha1 kind: ServiceExport metadata: name: serve --- apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: serve-export-policy spec: resourceSelectors: - apiVersion: multicluster.x-k8s.io/v1alpha1 kind: ServiceExport name: serve placement: clusterAffinity: clusterNames: - member1
4. 导出service到成员集群
apiVersion: multicluster.x-k8s.io/v1alpha1 kind: ServiceImport metadata: name: serve spec: type: ClusterSetIP ports: - port: 80 protocol: TCP --- apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: serve-import-policy spec: resourceSelectors: - apiVersion: multicluster.x-k8s.io/v1alpha1 kind: ServiceImport name: serve placement: clusterAffinity: clusterNames: - member2
5. 测试结果
在 member2 中,通过创建一个测试 pod 来请求 member1 中的 pod。过程中会通过 derived-serve 这个 service 来中转。
root@zishen:/home/btg/yaml/mcs# export KUBECONFIG="$HOME/.kube/members.config" root@zishen:/home/btg/yaml/mcs# kubectl config use-context member2 Switched to context "member2".
使用 member2 中服务的 ip 测试
root@zishen:/home/btg/yaml/mcs# kubectl get svc -A NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default derived-serve ClusterIP 10.13.166.120 <none> 80/TCP 4m37s default kubernetes ClusterIP 10.13.0.1 <none> 443/TCP 6d18h kube-system kube-dns ClusterIP 10.13.0.10 <none> 53/UDP,53/TCP,9153/TCP 6d18h #使用ip测试服务 root@zishen:/home/btg/yaml/mcs# kubectl --kubeconfig ~/.kube/members.config --context member2 run -i --rm --restart=Never --image=jeremyot/request:0a40de8 request -- --duration=3s --address=10.13.166.120 If you don't see a command prompt, try pressing enter. 2023/06/12 02:58:03 'hello from cluster member1 (Node: member1-control-plane Pod: serve-5899cfd5cd-6l27j Address: 10.10.0.17)' 2023/06/12 02:58:04 'hello from cluster member1 (Node: member1-control-plane Pod: serve-5899cfd5cd-6l27j Address: 10.10.0.17)' pod "request" deleted root@zishen:/home/btg/yaml/mcs#
测试成功,在 member1 中使用 k8s 默认域名 serve.default.svc.cluster.local
root@zishen:/home/btg/yaml/mcs# kubectl get svc -A NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default derived-serve ClusterIP 10.13.166.120 <none> 80/TCP 4m37s default kubernetes ClusterIP 10.13.0.1 <none> 443/TCP 6d18h kube-system kube-dns ClusterIP 10.13.0.10 <none> 53/UDP,53/TCP,9153/TCP 6d18h #使用ip测试服务 root@zishen:/home/btg/yaml/mcs# kubectl --kubeconfig ~/.kube/members.config --context member2 run -i --rm --restart=Never --image=jeremyot/request:0a40de8 request -- --duration=3s --address=10.13.166.120 If you don't see a command prompt, try pressing enter. 2023/06/12 02:58:03 'hello from cluster member1 (Node: member1-control-plane Pod: serve-5899cfd5cd-6l27j Address: 10.10.0.17)' 2023/06/12 02:58:04 'hello from cluster member1 (Node: member1-control-plane Pod: serve-5899cfd5cd-6l27j Address: 10.10.0.17)' pod "request" deleted
测试成功,在 member2 中使用影子域名 derived-serve.default.svc.cluster.local。
root@zishen:/home/btg/yaml/mcs# kubectl --kubeconfig ~/.kube/members.config --context member2 run -i --rm --restart=Never --image=jeremyot/request:0a40de8 request -- --duration=3s --address=derived-serve.default.svc.cluster.local If you don't see a command prompt, try pressing enter. 2023/06/12 03:30:41 'hello from cluster member1 (Node: member1-control-plane Pod: serve-5899cfd5cd-6l27j Address: 10.10.0.17)' 2023/06/12 03:30:42 'hello from cluster member1 (Node: member1-control-plane Pod: serve-5899cfd5cd-6l27j Address: 10.10.0.17)' pod "request" deleted
从官网例子可以看出,也就是 Karmada 的 v1.6.1目前的跨集群访问方式是:在需要的集群中部署 ServiceImport,生成影子服务 derived-服务名;再通过 derived-服务名.default.svc.cluster.local 来实现跨集群服务的访问。
这和使用统一域名访问的目标还存在差距,访问者还需要感知访问方式。
二、探索各集群使用一致域名
1. 各方案说明
-
使用影子服务
即在客户端集群创建一样名字的服务,但 endpointSlice 的 ENDPOINTS 却指向目标 pods。这类方式需要解决本地同名服务的冲突,Karmada 社区 bivas【3】的 pr【4】正在讨论此方案。
-
采用拓展coreDNS的方式
类似 submariner 的SERVICE DISCOVERY【5】的方式。需要开发一个拓展的 dns 解析器,并在 coreDNS 中安装对应的插件将带有指定后缀的域名(如 clusterset.local)转到这个 dns 扩展解析器。从而实现域名的解析。
▲流程图如上
-
可以实现优先访问本地服务。由于自定义了扩展 dns 解析器,故客户可在此实现服务访问的优先策略。
-
可以实现按照原有方式访问。自定义插件中可以决定对本地服务和远端服务的规则。故可以保持原有的访问方式,本例中为:foo.default.svc.cluster.local。
-
不需要 dns 的其他配置。由于有自定义的 dns 解析器,无需对 dns search进行配置。故没有方案一中对 dns search的配置。
-
同样需要修改 coredns 组件。也需要重新编译 coredns 以便安装对应的插件,也需要修改对应的 coredns 配置。
-
需要在成员集群中部署新组件。该方案需要安装一个dns扩展解析器。
-
使用SeviseImport
其原理为 1645-multi-cluster-services-api【6】或 [Multi-Cluster DNS 【7】本文所介绍的 coredns 的插件 multicluster【8】即是这类的实现。下面将对其的实现进行探索分析。
2. coredns的multicluster插件探索
multicluster 插件的原理比较简单,需要客户端集群的 ServiceImport 拥有原始服务的名称和对应需要解析的 clusterIP(这个ip可以是原始集群的--需要打通,也可以是本集群的)。multicluster 将这些信息生成coredns的rr记录。在遇到 multicluster 需要解析的域名时候,即可完成解析。
2.1 编译安装coreDNS的multicluster插件
编译带 multicluster 插件的 coredns
按照官网的文档 multicluster插件【9】下载coredns,k8s 1.26对应版本为v1.9.3(实测最新的v1.10.1也可):
git clone https://github.com/coredns/coredns cd coredns git checkout v1.9.3
添加 multicluster 插件,打开 plugin.cfg 文件,添加 multicluster
kubernetes:kubernetes
multicluster:github.com/coredns/multicluster
执行编译
cd ../
make
直接在目录下执行(使用coreDNS v1.10以下版本,否则需要升级docker版本):
root@zishen:/usr/workspace/go/src/github.com/coredns# docker build -f Dockerfile -t registry.k8s.io/coredns/coredns:v1.9.3 ./
查看
root@zishen:/usr/workspace/go/src/github.com/coredns# docker images|grep core registry.k8s.io/coredns/coredns v1.9.3 9a15fc60cfea 27 seconds ago 49.8MB
load到kind中:
root@zishen:/usr/workspace/go/src/github.com/coredns# kind load docker-image registry.k8s.io/coredns/coredns:v1.9.3 --name member2 Image: "registry.k8s.io/coredns/coredns:v1.9.3" with ID "sha256:9a15fc60cfea3f7e1b9847994d385a15af6d731f86b7f056ee868ac919255dca" not yet present on node "member2-control-plane", loading... root@zishen:/usr/workspace/go/src/github.com/coredns#
配置 member2 中的 coredns 权限
kubectl edit clusterrole system:coredns
root@zishen:/usr/workspace/go/src/github.com/coredns# kind load docker-image registry.k8s.io/coredns/coredns:v1.9.3 --name member2 Image: "registry.k8s.io/coredns/coredns:v1.9.3" with ID "sha256:9a15fc60cfea3f7e1b9847994d385a15af6d731f86b7f056ee868ac919255dca" not yet present on node "member2-control-plane", loading... root@zishen:/usr/workspace/go/src/github.com/coredns#
配置 multicluster 的 zone 规则
Corefile: | .:53 { errors health { lameduck 5s } ready multicluster clusterset.local kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance }
【注意】clusterset.local不能为系统默认的cluster.local,否则会被multicluster之前的kubernetes插件拦截。若一定需要,需要在编译前,在plugin.cfg文件中把multicluster放到kubernetes插件前。但造成的影响还未完全测试,需要进一步分析。
root@zishen:/home/btg/yaml/mcs# kubectl delete pod -n kube-system coredns-787d4945fb-mvsv4 pod "coredns-787d4945fb-mvsv4" deleted root@zishen:/home/btg/yaml/mcs# kubectl delete pod -n kube-system coredns-787d4945fb-62nxv pod "coredns-787d4945fb-62nxv" deleted
由于当前 Karmada 还未填充 serviceImport 的 ips 字段,故需要我们自己填写。删除member2 的 ServiceImport,在 karmada 控制面删除 ServiceImport。具体 yaml 参考本文之前的(导入Service到成员集群)。创建新的 ServiceImport,由于 member2 中已经没有 ServiceImport,故没了影子服务(没有clusterip)。为调试,ips 暂时用源端的 pod ip。
新的 ServiceImport 的 yaml 如下:
apiVersion: multicluster.x-k8s.io/v1alpha1 kind: ServiceImport metadata: name: serve namespace: default spec: type: ClusterSetIP ports: - name: "http" port: 80 protocol: TCP ips: - "10.10.0.5"
切换到member2里面创建该ServiceImport。
创建好后效果如下:
root@zishen:/home/btg/yaml/mcs/test# kubectl get serviceImport -A NAMESPACE NAME TYPE IP AGE default serve ClusterSetIP ["10.10.0.5"] 4h42m
验证
可以使用(之前介绍的方法:测试结果),但为了调试方便,本文使用直接创建客户端 pod 的方式。
在 member2 上创建 pod
kubectl --kubeconfig ~/.kube/members.config --context member2 run -i --image=ubuntu:18.04 btg
创建好后,ctrl+c 退出。进入这个 pod
kubectl exec -it -n default btg bash
安装软件
apt-get update apt-get install dnsutils apt install iputils-ping apt-get install net-tools apt install curl apt-get install vim
测试可配置域名,在 /etc/resolv.conf 中添加 clusterset.local 后缀。(解释详细点)
root@btg:/# cat /etc/resolv.conf search default.svc.cluster.local svc.cluster.local cluster.local clusterset.local nameserver 10.13.0.10 options ndots:5
访问域名:serve.default.svc.clusterset.local
root@btg:/# curl serve.default.svc.clusterset.local:8080 'hello from cluster member1 (Node: member1-control-plane Pod: serve-5899cfd5cd-dvxz8 Address: 10.10.0.7)'root@btg:/#
【注意】由于我们使用的域名不是clusterIP,故需要加上端口8080
测试成功,故后续在 Karmada中实现对集群成员 ServiceImport 的 ips 添加即可。
访问方式说明
由于使用了 dns 的 search 功能,故其访问与其保持一致。若服务为 foo,则访问方式为:
-
foo.default.svc 访问。该方式会按照 coredns 的 searches 配置顺序进行解析。可实现在无本地同名服务时,访问远端集群服务;本地服务存在时(与该服务是否可用无关),则访问本地服务。
-
foo.default.svc.clusterset.local 全域名访问。该请求会直接转到远端服务处理。
缺点
-
使用 foo.default.svc 访问并不能做到优先本地服务。只会在无本地同名服务时,才会访问远端。故,不能满足本地服务不可用时,再使用远端集群的服务的场景。
-
使用 foo.default.svc 方式还需要修改 dns 配置。若是使用 resolv.conf方式,则需要修改每台可能的服务端服务的resolv.conf 文件。若是使用 dnsConfig 方式,则需要修改下发 pod 的 yaml 或 api。
-
不能动态加载 multicluster 插件。需要客户重新编译 coredns,并且修改成员集群的 coredns 配置。原有访问方式会被改变。该方案采用不同后缀的方式来区分本地,远端服务。故原有的访问方式 foo.default.svc.cluster.local 会变为 foo.default.svc.clusterset.local。客户需要修改代码来适配。
-
影响可控。不会新增组件,coredns 增加multicluster的方式也是遵守coredns 官方提案,且代码公开。
-
修改量小,使用 multicluster 已有代码和方案即可完成。
三、问题记录
1. Failed to watch *v1alpha1.Servicelmport
W0612 12:18:13.939070 1 reflector.go:324] pkg/mod/k8s.io/client-go@v0.24.0/tools/cache/reflector.go:167: failed to list *v1alpha1.ServiceImport: serviceimports.multicluster.x-k8s.io is forbidden: User "system:serviceaccount:kube-system:coredns" cannot list resource "serviceimports" in API group "multicluster.x-k8s.io" at the cluster scope E0612 12:18:13.939096 1 reflector.go:138] pkg/mod/k8s.io/client-go@v0.24.0/tools/cache/reflector.go:167: Failed to watch *v1alpha1.ServiceImport: failed to list *v1alpha1.ServiceImport: serviceimports.multicluster.x-k8s.io is forbidden: User "system:serviceaccount:kube-system:coredns" cannot list resource "serviceimports" in API group "multicluster.x-k8s.io" at the cluster scope
解决方法:添加RBAC权限:
root@zishen:/home/btg/yaml/mcs# kubectl edit clusterrole system:coredns # Please edit the object below. Lines beginning with a '#' will be ignored, # and an empty file will abort the edit. If an error occurs while saving this file will be # reopened with the relevant failures. # apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: creationTimestamp: "2023-06-12T07:50:29Z" name: system:coredns resourceVersion: "225" uid: 51e7d961-29a6-43dc-ac0f-dbca68271e46 rules: - apiGroups: - "" resources: - endpoints - services - pods - namespaces - ServiceImport verbs: - list - watch ... - apiGroups: - multicluster.x-k8s.io resources: - serviceimports verbs: - list - watch
2. 编译 coredns的master分支的镜像报:invalid argument
二进制编译好后,编译镜像的具体报错如下:
failed to parse platform : "" is an invalid component of "": platform specifier component must match "
【注意】不能直接apt-get install docker-ce ,否则装出来的是20版本,还是有这个问题。
-
apt切换源
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
-
编写 /etc/apt/sources.list.d/docker.list 文件(没有就新增),添加如下内容:
deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable
-
执行更新
deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable apt-get update sudo apt-get upgradeapt-get update sudo apt-get upgrade
-
安装docker
apt-get install docker-ce docker-ce-cli containerd.io
3. dig中报WARNING:recursion requested but not available
root@btg:/# dig @10.13.0.10 serve.default.svc.cluster.local A ; <<>> DiG 9.11.3-1ubuntu1.18-Ubuntu <<>> @10.13.0.10 serve.default.svc.cluster.local A ; (1 server found) ;; global options: +cmd ;; Got answer: ;; WARNING: .local is reserved for Multicast DNS ;; You are currently testing what happens when an mDNS query is leaked to DNS ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 57327 ;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ; COOKIE: 24c073ed74240563 (echoed) ;; QUESTION SECTION: ;serve.default.svc.cluster.local. IN A
参考header插件,在corefile中增加即可
header { response set ra # set RecursionAvailable flag }
四、要求
1、coredns 版本必须为 v1.9.3及其以上,否则不支持 multicluster 特性。配套的 K8s 版本至少 v1.21.0。
2、服务中最好指定 dnsPolicy: ClusterFirst。
五、参考
[1] 官网例子,详细操作如下:https://karmada.io/zh/docs/userguide/service/multi-cluster-service
[2] Multi-ClusterServicesAPI:https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/README.md
[3] Karmada 社区bivas:https://github.com/bivas
[4] proposal for native service discovery:https://github.com/karmada-io/karmada/pull/3694
[5] SERVICE DISCOVERY:https://submariner.io/getting-started/architecture/service-discovery/
[6] 1645-multi-cluster-services-api:https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api
[7] Multi-Cluster DNS :https://docs.google.com/document/d/1-jy1WM4Tb4iz4opBTxviap5PnpfRWd-NZvwhmZWOpRk/edit#heading=h.uk21d87cd2uk
[8] multicluster:https://github.com/coredns/multicluster
[9] multicluster插件:https://github.com/coredns/multicluster
[10] CoreDNS篇2-编译安装ExternalPlugins]https://zhuanlan.zhihu.com/p/387807927
[11] MCP多云跨集群pod互访
https://www.361way.com/mcp-multi-pod-access/6873.html
[12] 采坑指南——K8s域名解析coredns问题排查过程
https://zhuanlan.zhihu.com/p/89898164
▍相关学习
1、Submariner 原理说明:【Kubernetes CNI 插件选型和应用场景探讨:
https://zhuanlan.zhihu.com/p/582102994
2、coredns原理:【K8s 服务注册与发现(三)CoreDNS :
https://cloud.tencent.com/developer/article/2126512
3、service原理:【浅谈 Kubernetes Service:
https://z.itpub.net/article/detail/A0463024894EF7FEEDBFC4DDF0E797C8
4、endpointSlice原理:【重识云原生】第六章容器基础 6.4.9.5 节—— Service特性端点切片(Endpoint Slices):
https://blog.csdn.net/junbaozi/article/details/127857965
号外!
华为将于2023年9月20-22日,在上海世博展览馆和上海世博中心举办第八届华为全联接大会(HUAWEICONNECT 2023)。本次大会以“加速行业智能化”为主题,邀请思想领袖、商业精英、技术专家、合作伙伴、开发者等业界同仁,从商业、产业、生态等方面探讨如何加速行业智能化。
我们诚邀您莅临现场,分享智能化的机遇和挑战,共商智能化的关键举措,体验智能化技术的创新和应用。您可以:
- 在100+场主题演讲、峰会、论坛中,碰撞加速行业智能化的观点
- 参观17000平米展区,近距离感受智能化技术在行业中的创新和应用
- 与技术专家面对面交流,了解最新的解决方案、开发工具并动手实践
- 与客户和伙伴共寻商机
感谢您一如既往的支持和信赖,我们热忱期待与您在上海见面。
大会官网:https://www.huawei.com/cn/events/huaweiconnect
欢迎关注“华为云开发者联盟”公众号,获取大会议程、精彩活动和前沿干货。