?DNS在Kubernetes中的高階玩法(一)


云原生小白
看到上面藍(lán)色字了么?關(guān)注下吧!
自從 Kubernetes1.11 之后,CoreDNS 作為集群內(nèi)默認(rèn)的域名解析服務(wù),你是否對它還僅僅還停留在對 Kubernetes 的 Service 解析呢?事實上光 DNS 在 K8S 內(nèi)就有很多有意思的操作,今天我們不妨來看看 CoreDNS 的各種高階玩法。
1. 自定義 hosts 解析
默認(rèn)情況下,Kubernetes 集群內(nèi)的容器要解析外部域名時,CoreDNS 會將請求轉(zhuǎn)發(fā)給/etc/resolv.conf文件里指定的上游 DNS 服務(wù)器。這個是由這個配置決定的。
forward?.?/etc/resolv.conf
有的時候,我們?nèi)绻枰?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(245, 70, 0);">集群內(nèi)全局劫持某個域名時,我們通??梢岳?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(245, 70, 0);">hosts插件來幫忙。hosts插件會每隔 5s 將需解析的信息重新加載到 coredns 當(dāng)中,當(dāng)你有任何變化時直接更新它的配置區(qū)塊即可。常見的 host 有兩種方法配置,分別如下:
定義 host
.:53?{
????hosts?{
????????1.1.1.1?test.cloudxiaobai.com
????????2.2.2.2?test2.cloudxiaobai.com
????????fallthrough
????}
}
加載 hosts 文件
#直接從/etc/hosts加載host信息
.?{
????hosts?{
????????fallthrough
????}
}
#又或者,從當(dāng)前目錄的test.hosts文件中加載host信息
.?{
????hosts?test.hosts?{
????????fallthrough
????}
}
當(dāng)被需要解析的域名不在 hosts 當(dāng)中時,需要用
fallthrough繼續(xù)將請求轉(zhuǎn)發(fā)給其它插件繼續(xù)處理
擴展
如果我們只是想在 Workloads 內(nèi)局部生效部分 host 信息時,那么可以借助于HostAliases向Pod的/etc/hosts文件內(nèi)添加主機信息。我們拿 deployment 來舉例,
apiVersion:?extensions/v1beta1
kind:?Deployment
spec:
??template
????spec:
??????containers:
??????-?image:?busybox:latest
????????name:?nginx
??????hostAliases:
??????-?ip:?1.1.1.1
????????hostnames:
????????-?test1.cloudxiaobai.com
??????-?ip:?2.2.2.2
????????hostnames:
????????-?test1.cloudxiaobai.com
...
2. 支持 SRV 記錄
SRV 記錄是域名系統(tǒng)中用于指定服務(wù)器提供服務(wù)的位置(如主機名和端口)數(shù)據(jù)。它在 DNS 記錄中的是個新鮮面孔,在 RFC2082 中才對 SRV 記錄進行了定義,因此有很多老舊服務(wù)器并不支持SRV記錄。SRV 在 RFC2082 定義的標(biāo)準(zhǔn)記錄格式如下:
#英文
_Service._Proto.Name?TTL?Class?SRV?Priority?Weight?Port?Target
#中文
_服務(wù)._協(xié)議.名稱.?TTL?類別?SRV?優(yōu)先級?權(quán)重?端口?主機.
Service :服務(wù)的符號名稱 Proto :服務(wù)的傳輸協(xié)議,通常為 TCP 或 UDP,Proto 不區(qū)分大小寫 Name :此 RR 所指的域名,在這個域名下 SRV RR 是唯一的 TTL :標(biāo)準(zhǔn) DNS 存活時間 CLASS :標(biāo)準(zhǔn) DNS 類別值(此值總為 IN) Priority :目標(biāo)主機的優(yōu)先級,值越小越優(yōu)先,范圍 0-65535 Weight :相同優(yōu)先度記錄的相對權(quán)重,值越大越優(yōu)先 Port :服務(wù)所在的 TCP 或 UDP 端口 Target : 提供服務(wù)的規(guī)范主機名,以半角句號結(jié)尾
在 Kubernetes 里面,CoreDNS 會為有名稱的端口創(chuàng)建SRV記錄,這些端口可以是 svc 或 headless.svc 的一部分。對每個命名端口,SRV 記錄了一個類似下列格式的記錄:
_port-name._port-protocol.my-svc.my-namespace.svc.cluster.local
在 Golang 中我們用 net.LookupSRV 來發(fā)起 SRV 記錄查詢
func?(r?*Resolver)?LookupSRV(ctx?context.Context,?service,?proto,?name?string)?(cname?string,?addrs?[]*SRV,?err?error)
net 庫里對 SRV 結(jié)構(gòu)體里定義了 4 個字段,分別是Target,Port,Priority,Wright。當(dāng)我們使用LookupSRV發(fā)起 SRV 查詢時,得到的返回的記錄會按優(yōu)先級排序,并在優(yōu)先級內(nèi)按權(quán)重進行隨機分配。如果 service 和 proto 均為空字符串,則 LookupSRV 直接查找 name。
拿 thanos 的 SRV 查詢舉個例子
1. 第一步 resolver.go 中 SRV 查詢邏輯
thanos 中的 resolver.go 里面包含了處理 SRV 查詢的邏輯,如下:
?case?SRV,?SRVNoA:
??_,?recs,?err?:=?s.resolver.LookupSRV(ctx,?"",?"",?host)
??if?err?!=?nil?{
???return?nil,?errors.Wrapf(err,?"lookup?SRV?records?%q",?host)
??}
??for?_,?rec?:=?range?recs?{
???resPort?:=?port
???if?resPort?==?""?{
???//獲取SRV返回的端口
????resPort?=?strconv.Itoa(int(rec.Port))
???}
???if?qtype?==?SRVNoA?{
?????????//如果不需要使用A或者AAAA記錄查詢時,則組合主機名:端口
????res?=?append(res,?appendScheme(scheme,?net.JoinHostPort(rec.Target,?resPort)))
????continue
???}
???//?Do?A?lookup?for?the?domain?in?SRV?answer.
???resIPs,?err?:=?s.resolver.LookupIPAddr(ctx,?rec.Target)
???if?err?!=?nil?{
????return?nil,?errors.Wrapf(err,?"look?IP?addresses?%q",?rec.Target)
???}
???//根據(jù)主機名遍歷出所有的ip地址,并組合成ip:port的方式
???for?_,?resIP?:=?range?resIPs?{
????res?=?append(res,?appendScheme(scheme,?net.JoinHostPort(resIP.String(),?resPort)))
???}
第二步 創(chuàng)建 Kubernetes Service
CoreDNS 中對于有名稱的 port,會為其創(chuàng)建一條對應(yīng)的 SRV 記錄。
apiVersion:?v1
kind:?Service
metadata:
??labels:
????app:?thanos-receiver
??name:?thanos-receiver
??namespace:?monitor
spec:
??ClusterIP:?None
??ports:
??-?name:?grpc
????port:?10901
????protocol:?TCP
????targetPort:?10901
如上結(jié)構(gòu)的 Service 我們就可以使用如下方式查詢域名:
_grpc._tcp.thanos-receiver.monitor.svc.cluster.local
我們用 dig 命令做一次 SRV 記錄查詢就可以得到響應(yīng),可以看到下列的ANSWER SECTION中得到了 net 庫里面定義的 SRV 結(jié)構(gòu)體的數(shù)據(jù)了,并且 CoreDNS 返回了三條記錄:
#?dig?srv?_grpc._tcp.thanos-receiver.monitor.svc.cluster.local
...
;;?ANSWER?SECTION:
_grpc._tcp.thanos-receiver.monitor.svc.cluster.local.?6?IN?SRV?0?33?10901?10-59-155-238.thanos-receiver.monitor.svc.cluster.local.
_grpc._tcp.thanos-receiver.monitor.svc.cluster.local.?6?IN?SRV?0?33?10901?10-59-42-68.thanos-receiver.monitor.svc.cluster.local.
_grpc._tcp.thanos-receiver.monitor.svc.cluster.local.?6?IN?SRV?0?33?10901?10-59-48-162.thanos-receiver.monitor.svc.cluster.local.
;;?ADDITIONAL?SECTION:
10-59-48-162.thanos-receiver.monitor.svc.cluster.local.?6?IN?A?10.59.48.162
10-59-42-68.thanos-receiver.monitor.svc.cluster.local.?6?IN?A?10.59.42.68
10-59-155-238.thanos-receiver.monitor.svc.cluster.local.?6?IN?A?10.59.155.238
...
可以看到這條 SRV 記錄里面,分別返回了三個服務(wù)的IP地址、端口、以及服務(wù)的優(yōu)先級和權(quán)重
第三步 使用 SRV 記錄做服務(wù)發(fā)現(xiàn)
對于代碼中啟用了 SRV 記錄的業(yè)務(wù),只需要在業(yè)務(wù)配置里面加上需要訪問的 SRV 地址即可,例如 thanos-query 需要調(diào) thanos-receiver 的 grpc 端口做監(jiān)控數(shù)據(jù)查詢,如果我們集群內(nèi)有多個 receiver 服務(wù)的話,我們就像如下配置,即可做到 DNS 的服務(wù)發(fā)現(xiàn):
...
????spec:
??????containers:
??????-?args:
????????-?query
????????#?定義thanos-receiver服務(wù)SRV記錄
????????-?--store=dnssrv+_grpc._tcp.thanos-receiver.monitor.svc.cluster.local
...
當(dāng)服務(wù)正常運行后我們就可以查到 receiver 服務(wù)以及注冊到 query 里面了

3. NodeLocal DNSCache
有很多同學(xué)經(jīng)常會抱怨,在 Kubernetes 中有時候會遇到 DNS 解析間歇性 5s 超時的問題。其實這個問題社區(qū)很早意識到 DNS 的經(jīng)過 Iptables 到 Conntrack 遇到競爭的問題,并給出來利用 Daemonset 在集群的每個 Node 上運行一個精簡版的 CoreDNS 并監(jiān)聽一個虛擬 ip 地址來繞過 Conntrack,同時還能充當(dāng)緩存環(huán)境 CoreDNS 壓力。此舉能大幅降低 DNS 查詢 timeout 的頻次,提升服務(wù)穩(wěn)定性。
關(guān)于部署
node-local-dns通過添加 iptables 規(guī)則能夠接收節(jié)點上所有發(fā)往 169.254.20.10 的 dns 查詢請求,把針對集群內(nèi)部域名查詢請求路由到 coredns。把集群外部域名請求直接通過 host 網(wǎng)絡(luò)發(fā)往本地/etc/resolv.conf記錄的外部 DNS 服務(wù)器中。
#?下載部署腳本
$?curl?https://node-local-dns.oss-cn-hangzhou.aliyuncs.com/install-nodelocaldns.sh
#?部署,確保kubectl能夠連接集群
$?bash?install-nodelocaldns.sh
如何使用
NodeLocal DNSCache的部署并不會直接產(chǎn)生效果,通常我們有兩種方式可以讓集群的 pod 使用上本機 DNS 緩存。
1. 定制業(yè)務(wù)容器 dnsConfig
Kubernetes 的 workload 中允許我們自定義 dns 相關(guān)的配置,其中我們需要注意以下幾點:
dnsPolicy: None,不使用 ClusterDNS。 配置 searches,保證集群內(nèi)部域名能夠被正常解析。 適當(dāng)降低 ndots 值,當(dāng)前 ACK 集群 ndots 值默認(rèn)為 5,降低 ndots 值有利于加速集群外部域名訪問。 適當(dāng)調(diào)整 options 參數(shù),避免并發(fā)請求 single-request和分開 A 和 AAAA 請求采用的源端口single-request-reopen
可以參考如下
dnsPolicy:?None
dnsConfig:
????nameservers:?["169.254.20.10"]
????searches:
????-?default.svc.cluster.local
????-?svc.cluster.local
????-?cluster.local
????options:
????-?name:?ndots
??????value:?"2"
????-?name:?single-request-reopen
??????value:?""
????-?name:?timeout
??????value:?"1"
2. 修改 Kubelet 配置
kubelet 啟動參數(shù)中可以通過參數(shù)--cluster-dns來指定容器的 nameserver,我們只需將它修改成169.254.20.10重啟即可。不過容器要真正將 NodeLocal DNSCache 用起來話,還得將Pod重啟才會生效。
4. 禁用 IPv6 域名解析
有時候我們 Kubernetes 集群內(nèi)沒有啟用 IPv6 的話,可以在 CoreDNS 內(nèi)禁止 IPv6 的域名解析,這個時候我們可以用 Template 這個插件來解決:
.:53?{
????template?ANY?AAAA?{
????????rcode?NXDOMAIN
????}
...
}
這條記錄會將所有的 AAAA 查詢直接返回
NXDOMAIN,并且不會被轉(zhuǎn)發(fā)給其它插件處理
--- 未完待續(xù) ---
點贊鼓勵一下

