CoreDNS 與多集群服務 MCS
Kubernetes 作為一項核心技術已成為現代應用程序架構的基礎,越來越多的企業使用其作為容器編排系統。Kubernetes 集群經歷了 從單 Kubernetes 集群到多 Kubernetes 集群、從多 Kubernetes 集群到 Kubernetes 多集群的演進[1],集群的展現形式不斷發生著變化。
為此,Kubernetes 多集群 SIG 提出了 KEP-1645: Multi-Cluster Services API[2](以下簡稱 MCS API)應對 Kubernetes 多集群帶來的挑戰,詳細內容可以看之前的介紹:認識一下 Kubernetes 多集群服務 API[3] ,這篇要介紹的是多集群服務 DNS。
多集群服務 DNS
在官方建議的方案中使用了 多集群服務 DNS[4] ,并提出了 Kubernetes 基于 DNS 的多集群服務發現規范[5]用于指導多集群服務 API 的實現。該規范可以認為是 Kubernetes 的基于 DNS 的服務發現規范[6] 的擴展。
簡單來說,該規范引入了新的 DNS 搜索域 clusterset.local,在 DNS 解析 <service>.<ns>.svc.clusterset.local 時,返回 ServiceImport 中的 IP 地址。
圖片
CoreDNS 的 multicluster[7] 插件實現了該邏輯。
CoreDNS 多集群插件
插件可以說是 CoreDNS 精髓,其插件可以分為兩種:核心插件[8] 和 外部插件(External Plugin)[9]。
- 核心插件,也就是通常所說的插件,默認是編譯在 CoreDNS 中的,只需在 Corefile 中添加配置即可啟用該插件。
- 外部插件,不是 CoreDNS 默認支持的,需要編譯時加入到 CoreDNS 中。下面會詳細介紹如何將外部插件編譯到 CoreDNS 中。
multicluster 插件是眾多外部插件中的一個,實現了基于 DNS 的多集群服務發現規范。
工作原理
multicluster 插件在啟動時 創建一個控制器[10],用于監控 ServiceImports[11] 資源;在處理 DNS 解析請求階段,只處理 clusterset.local 域的解析請求:從所有 ServiceImports 資源中 檢索出匹配的結果[12],返回其中的 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 的網絡互通問題,有興趣可以看 這篇。
不知道你是否有更好的方案?歡迎留言討論。
參考資料
[1] 從單 Kubernetes 集群到多 Kubernetes 集群、從多 Kubernetes 集群到 Kubernetes 多集群的演進: https://atbug.com/kubernetes-cluster-evolution-in-multi-hybrid-cloud/
[2] KEP-1645: Multi-Cluster Services API: https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#kep-1645-multi-cluster-services-api
[3] 認識一下 Kubernetes 多集群服務 API: https://atbug.com/kubernetes-multi-cluster-api/
[4] 多集群服務 DNS: https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/README.md#dns
[5] Kubernetes 基于 DNS 的多集群服務發現規范: https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/specification.md
[6] Kubernetes 的基于 DNS 的服務發現規范: https://github.com/kubernetes/dns/blob/master/docs/specification.md
[7] multicluster: https://coredns.io/explugins/multicluster/
[8] 核心插件: https://coredns.io/plugins/
[9] 外部插件(External Plugin): https://coredns.io/explugins
[10] 創建一個控制器: https://github.com/coredns/multicluster/blob/3afa45c2996fd02d1d781b74c58da0d0e6855f18/multicluster.go#L69
[11] ServiceImports: https://github.com/coredns/multicluster/blob/3afa45c2996fd02d1d781b74c58da0d0e6855f18/controller.go#L95
[12] 檢索出匹配的結果: https://github.com/coredns/multicluster/blob/3afa45c2996fd02d1d781b74c58da0d0e6855f18/controller.go#L300