CoreDNS 与多集群服务 MCS
Kubernetes 作为一项核心技术已成为现代应用程序架构的基础,越来越多的企业使用其作为容器编排系统。Kubernetes 集群经历了 从单 Kubernetes 集群到多 Kubernetes 集群、从多 Kubernetes 集群到 Kubernetes 多集群的演进,集群的展现形式不断发生着变化。
为此,Kubernetes 多集群 SIG 提出了 KEP-1645: Multi-Cluster Services API(以下简称 MCS API)应对 Kubernetes 多集群带来的挑战,详细内容可以看之前的介绍:认识一下 Kubernetes 多集群服务 API ,这篇要介绍的是多集群服务 DNS。
多集群服务 DNS
在官方建议的方案中使用了 多集群服务 DNS ,并提出了 Kubernetes 基于 DNS 的多集群服务发现规范 用于指导多集群服务 API 的实现。该规范可以认为是 Kubernetes 的基于 DNS 的服务发现规范 的扩展。
简单来说,该规范引入了新的 DNS 搜索域 clusterset.local
,在 DNS 解析 <service>.<ns>.svc.clusterset.local
时,返回 ServiceImport
中的 IP 地址。
CoreDNS 的 multicluster 插件实现了该逻辑。
CoreDNS 多集群插件
插件可以说是 CoreDNS 精髓,其插件可以分为两种:核心插件 和 外部插件(External Plugin)。
- 核心插件,也就是通常所说的插件,默认是编译在 CoreDNS 中的,只需在 Corefile 中添加配置即可启用该插件。
- 外部插件,不是 CoreDNS 默认支持的,需要编译时加入到 CoreDNS 中。下面会详细介绍如何将外部插件编译到 CoreDNS 中。
multicluster 插件是众多外部插件中的一个,实现了基于 DNS 的多集群服务发现规范。
工作原理
multicluster 插件在启动时 创建一个控制器,用于监控 ServiceImports 资源;在处理 DNS 解析请求阶段,只处理 clusterset.local
域的解析请求:从所有 ServiceImports 资源中 检索出匹配的结果,返回其中的 IP 地址,甚至端口。
演示
编译 CoreDNS
修改 plugin.cfg
,添加:
...
kubernetes:kubernetes
multicluster: github.com/coredns/multicluster
...
执行命令 make
进行编译后,然后执行命令检查插件是否编译成功:
./coredns --plugins | grep -A1 dns.minimal
dns.minimal
dns.multicluster
在结果中我们有看到 dns.multicluster
说明配置都成功了。
接下来就是构建多平台的二进制文件了了:
make build -f Makefile.release
可以再检查下编译好的二进制文件(使用对应平台的版本,我这里是 Ubuntu):
./build/linux/amd64/coredns --plugins | grep -A1 mini
dns.minimal
dns.multicluster
构建镜像
构建镜像之前记得执行下面的命令调整下文件目录:
cp -r build/linux build/docker
for arch in amd64 arm arm64 mips64le ppc64le s390x; do
cp Dockerfile build/docker/${arch} ;
done;
执行命令构建并推送镜像,这里 DOCKER=addozhang
是镜像 repository 名字,NAME=coredns-multicluster
是镜像名,按照下面的配置最终构建的镜像是 addozhang/coredns-multicluster:1.10.1
:
export DOCKER_LOGIN=addozhang
export DOCKER_PASSWORD=<pass>
make docker-build docker-push DOCKER=addozhang NAME=coredns-multicluster VERSION=1.10.1 -f Makefile.docker
更新 CoreDNS
因为这次演示不会涉及 ServiceExport
,可以只部署 ServiceImport
CRD。
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/mcs-api/master/config/crd/multicluster.x-k8s.io_serviceimports.yaml
修改了 ClusterRole coredns
,增加 multicluster.x-k8s.io
的 serviceimports
资源的操作权限。
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: system:coredns
rules:
- apiGroups:
- ""
resources:
- endpoints
- services
- pods
- namespaces
verbs:
- list
- watch
- apiGroups: #add serviceimports resource verbs
- multicluster.x-k8s.io
resources:
- serviceimports
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
EOF
将集群 CoreDNS 的镜像改为上面构建的镜像:
kubectl patch deployment coredns -n kube-system -p '{"spec":{"template":{"spec":{"containers":[{"name":"coredns","image":"addozhang/coredns-multicluster:1.10.1"}]}}}}'
只更新 CoreDNS 还不够,还需要修改 Corefile,添加 multicluster
配置。
kubectl apply -n kube-system -f - <<EOF
apiVersion: v1
data:
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
}
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
EOF
测试
手动创建 ServiceImport 资源,将服务地址配置为我本地运行的 web 服务 192.168.1.174:8080
,来模拟其他集群的服务。类型设置为 ClusterSetIP
:
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceImport
metadata:
name: my-svc
namespace: default
spec:
ips:
- 192.168.1.174
type: "ClusterSetIP"
ports:
- name: http
protocol: TCP
port: 8080
sessionAffinity: None
接下来测试下多集群服务 DNS 的解析,运行一个 pod 来模拟客户端:
kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot -n default
查询 my-svc.default.svc.clusterset.local
的解析,测试 A 记录和 SRV 记录的解析:
dig +short my-svc.default.svc.clusterset.local
192.168.1.174
dig +short SRV my-svc.default.svc.clusterset.local
0 100 8080 my-svc.default.svc.clusterset.local.
应用也可以正常访问:
curl my-svc.default.svc.clusterset.local:8080
Hi, there!
总结
这篇文章中我们通过 DNS 解决了跨集群的服务发现问题,相信看到这里大家也发现了 DNS 方案的弊端:
- 引入了新的 DNS 域
clusterset.local
,需要改动应用代码。 - 两个集群间的网络要打通,对底层的网络架构要求高,如果涉及跨云跨数据中心的话这个问题会更加明显。
- 多集群服务 DNS 插件目前还属于 external plugin,还要像上面那样重新编译、部署 CoreDNS
其中,由于目前 KEP1645 还处于提案阶段,待正式发布之后,相信 #3 的问题待插件进入核心插件列表后可以解决。
而 #1 的问题,假如当前应用内的代码中是使用 NAME.NS
的方式的话,可以通过在 Pod /etc/resolv.conf
增加 clusterset.local
搜索域来解决。由于 Kubernetes 集群只支持一个 clusterDomain
,只能通过 pod.dnsConfig
在添加搜索域了。单又会来另一个问题:搜索域的增加势必带来性能的开销。
这里 #2 的可能最为棘手,需要调整底层的网络架构,使用扁平网络或者其他如 vxlan、隧道等方案。
我曾经在另一篇文章中介绍过使用 7 层网络解决 #2 的网络互通问题,有兴趣可以看 这篇。
不知道你是否有更好的方案?欢迎留言讨论。