CoreDNS:Kubernetes內部域名解析原理、弊端及優化方式

Kubernetes 中的 DNS

本篇主要儘可能詳盡的說明 Kubernetes 的DNS解析原理,以及 Kubernetes 集羣中 DNS 解析目前存在的弊端和優化方式。

在 Kubernetes 中,服務發現有幾種方式:
①:基於環境變量的方式
②:基於內部域名的方式

基本上,使用環境變量的方式很少,主要還是使用內部域名這種服務發現的方式。

其中,基於內部域名的方式,涉及到 Kubernetes 內部域名的解析,而 kubedns,是 Kubernetes 官方的 DNS 解析組件。從 1.11 版本開始,kubeadm 已經使用第三方的 CoreDNS 替換官方的 kubedns 作爲 Kubernetes 集羣的內部域名解析組件,我們的重點,是 CoreDNS,但是在開始 CoreDNS 之前,需要先了解下 kubedns,後續,會對這2個 DNS 組件做對比,分析它們的優劣勢。

Kubernetes 中的域名是如何解析的

在 Kubernetes 中,比如服務 a 訪問服務 b,對於同一個 Namespace下,可以直接在 pod 中,通過 curl b 來訪問。對於跨 Namespace 的情況,服務名後邊對應 Namespace即可。比如 curl b.default。那麼,使用者這裏邊會有幾個問題:

①:服務名是什麼?
②:爲什麼同一個 Namespace 下,直接訪問服務名即可?不同 Namespace 下,需要帶上 Namespace 纔行?
③:爲什麼內部的域名可以做解析,原理是什麼?

DNS 如何解析,依賴容器內 resolv 文件的配置

複製

1
2
3
4
cat /etc/resolv.conf

nameserver 10.233.0.3
search default.svc.cluster.local svc.cluster.local cluster.local

 

這個文件中,配置的 DNS Server,一般就是 K8S 中,kubedns 的 Service 的 ClusterIP,這個IP是虛擬IP,無法ping,但可以訪問。

複製

1
2
3
4
[root@node4 user1]# kubectl get svc -n kube-system
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
kube-dns               ClusterIP   10.233.0.3      <none>        53/UDP,53/TCP   270d
kubernetes-dashboard   ClusterIP   10.233.22.223   <none>        443/TCP         124d

 

所以,所有域名的解析,其實都要經過 kubedns 的虛擬IP 10.233.0.3 進行解析,不論是 Kubernetes 內部域名還是外部的域名。

Kubernetes 中,域名的全稱,必須是 service-name.namespace.svc.cluster.local 這種模式,服務名,就是Kubernetes中 Service 的名稱,所以,當我們執行下面的命令時:

複製

1
curl b

 

必須得有一個 Service 名稱爲 b,這是前提。
在容器內,會根據 /etc/resolve.conf 進行解析流程。選擇 nameserver 10.233.0.3 進行解析,然後,用字符串 “b”,依次帶入 /etc/resolve.conf 中的 search 域,進行DNS查找,分別是:

複製

1
2
// search 內容類似如下(不同的pod,第一個域會有所不同)
search default.svc.cluster.local svc.cluster.local cluster.local

 

b.default.svc.cluster.local -> b.svc.cluster.local -> b.cluster.local ,直到找到爲止。
所以,我們執行 curl b,或者執行 curl b.default,都可以完成DNS請求,這2個不同的操作,會分別進行不同的DNS查找步驟:

複製

1
2
3
4
5
6
7
// curl b,可以一次性找到(b +default.svc.cluster.local)
b.default.svc.cluster.local

// curl b.default,第一次找不到( b.default + default.svc.cluster.local)
b.default.default.svc.cluster.local
// 第二次查找( b.default + svc.cluster.local),可以找到
b.default.svc.cluster.local

 

So Questions

curl b,要比 curl b.default 效率高?

答案是肯定的,因爲 curl b.default,多經過了一次 DNS 查詢。
當執行 curl b.default,也就使用了帶有命名空間的內部域名時,容器的第一個 DNS 請求是

複製

1
2
// b.default + default.svc.cluster.local
b.default.default.svc.cluster.local

 

當請求不到 DNS 結果時,使用

複製

1
2
// b.default + svc.cluster.local
b.default.svc.cluster.local

 

進行請求,此時纔可以得到正確的DNS解析。

訪問外部域名走 search 域嗎

這個答案,不能說肯定也不能說否定,看情況,可以說,大部分情況要走 search 域。

我們以請求 youku.com 爲例,通過抓包的方式,看一看在某個容器中訪問 youku.com,進行的DNS查找的過程,都產生了什麼樣的數據包。注意:我們要抓DNS容器的包,就得先進入到DNS容器的網絡中(而不是發起DNS請求的那個容器)。

由於DNS容器往往不具備bash,所以無法通過 docker exec 的方式進入容器內抓包,我們採用其他的方式:

複製

1
2
3
4
5
6
// 1、找到容器ID,並打印它的NS ID
docker inspect --format "{{.State.Pid}}"  16938de418ac
// 2、進入此容器的網絡Namespace
nsenter -n -t  54438
// 3、抓DNS包
tcpdump -i eth0 udp dst port 53|grep youku.com

 

在其他的容器中,進行 youku.com 域名查找

複製

1
nslookup  youku.com 172.22.121.65

 

注意:nslookup命令的最後指定DNS服務容器的IP,是因爲,如果不指定,且DNS服務的容器存在多個的話,那麼DNS請求,可能會均分到所有DNS服務的容器上,我們如果只抓某單個DNS服務容器抓到的包,可能就不全了,指定IP後,DNS的請求,就必然只會打到單個的DNS容器。抓包的數據才完整。

可以看到類似如下結果:

複製

1
2
3
4
5
17:01:28.732260 IP 172.20.92.100.36326 > nodexxxx.domain: 4394+ A? youku.com.default.svc.cluster.local. (50)
17:01:28.733158 IP 172.20.92.100.49846 > nodexxxx.domain: 60286+ A? youku.com.svc.cluster.local. (45)
17:01:28.733888 IP 172.20.92.100.51933 > nodexxxx.domain: 63077+ A? youku.com.cluster.local. (41)
17:01:28.734588 IP 172.20.92.100.33401 > nodexxxx.domain: 27896+ A? youku.com. (27)
17:01:28.734758 IP nodexxxx.34138 > 192.168.x.x.domain: 27896+ A? youku.com. (27)

 

我們可以看到,在真正解析 youku.com 之前,經歷了 youku.com.default.svc.cluster.local. -> youku.com.svc.cluster.local. -> youku.com.cluster.local. -> youku.com.

這也就意味着有3次DNS請求,是浪費的無意義的請求。

爲何會出現DNS請求浪費的情況

這是因爲,在 Kubernetes 中,其實 /etc/resolv.conf 這個文件,並不止包含 nameserver 和 search 域,還包含了非常重要的一項:ndots。我們之前沒有提及這個項,也是希望再次能引起讀者重視。

複製

1
2
3
4
[root@xxxx-67f54c6dff-h4zxq /]# cat /etc/resolv.conf 
nameserver 10.233.0.3
search cicd.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

 

ndots:5,表示:如果查詢的域名包含的點“.”,不到5個,那麼進行DNS查找,將使用非完全限定名稱(或者叫絕對域名),如果你查詢的域名包含點數大於等於5,那麼DNS查詢,默認會使用絕對域名進行查詢。舉例來說:

如果我們請求的域名是,a.b.c.d.e,這個域名中有4個點,那麼容器中進行DNS請求時,會使用非絕對域名進行查找,使用非絕對域名,會按照 /etc/resolv.conf 中的 search 域,走一遍追加匹配:

複製

1
2
3
a.b.c.d.e.cicd.svc.cluster.local. ->
a.b.c.d.e.svc.cluster.local. ->
a.b.c.d.e.cluster.local.

 

直到找到爲止。如果走完了search域還找不到,則使用 a.b.c.d.e. ,作爲絕對域名進行DNS查找。

我們通過抓包分析一個具體案例:
域名中點數少於5個的情況:

複製

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 對域名 a.b.c.d.ccccc 進行DNS解析請求 
[root@xxxxx-67f54c6dff-h4zxq /]# nslookup  a.b.c.d.ccccc 172.22.121.65 
Server:         172.22.121.65
Address:        172.22.121.65#53

** server can't find a.b.c.d.ccccc: NXDOMAIN

// 抓包數據如下:
18:08:11.013497 IP 172.20.92.100.33387 > node011094.domain: 28844+ A? a.b.c.d.ccccc.cicd.svc.cluster.local. (54)
18:08:11.014337 IP 172.20.92.100.33952 > node011094.domain: 57782+ A? a.b.c.d.ccccc.svc.cluster.local. (49)
18:08:11.015079 IP 172.20.92.100.45984 > node011094.domain: 55144+ A? a.b.c.d.ccccc.cluster.local. (45)
18:08:11.015747 IP 172.20.92.100.54589 > node011094.domain: 22860+ A? a.b.c.d.ccccc. (31)
18:08:11.015970 IP node011094.36383 > 192.168.x.x.domain: 22860+ A? a.b.c.d.ccccc. (31)

// 結論:
// 點數少於5個,先走search域,最後將其視爲絕對域名進行查詢

 

域名中點數>=5個的情況:

複製

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 對域名 a.b.c.d.e.ccccc 進行DNS解析請求
[root@xxxxx-67f54c6dff-h4zxq /]# nslookup  a.b.c.d.e.ccccc 172.22.121.65 
Server:         172.22.121.65
Address:        172.22.121.65#53

** server can't find a.b.c.d.e.ccccc: NXDOMAIN

// 抓包數據如下:
18:10:14.514595 IP 172.20.92.100.34423 > node011094.domain: 61170+ A? a.b.c.d.e.ccccc. (33)
18:10:14.514856 IP node011094.58522 > 192.168.x.x.domain: 61170+ A? a.b.c.d.e.ccccc. (33)
18:10:14.515880 IP 172.20.92.100.49328 > node011094.domain: 267+ A? a.b.c.d.e.ccccc.cicd.svc.cluster.local. (56)
18:10:14.516678 IP 172.20.92.100.35651 > node011094.domain: 54181+ A? a.b.c.d.e.ccccc.svc.cluster.local. (51)
18:10:14.517356 IP 172.20.92.100.33259 > node011094.domain: 53022+ A? a.b.c.d.e.ccccc.cluster.local. (47)

// 結論:
// 點數>=5個,直接視爲絕對域名進行查找,只有當查詢不到的時候,才繼續走 search 域。

 

如何優化 DNS 請求浪費的情況

優化方式1:使用全限定域名

其實最直接,最有效的優化方式,就是使用 “fully qualified name”,簡單來說,使用“完全限定域名”(也叫絕對域名),你訪問的域名,必須要以 “.” 爲後綴,這樣就會避免走 search 域進行匹配,我們抓包再試一次:

複製

1
2
// 注意:youku.com 後邊有一個點 .
nslookup  youku.com. 172.22.121.65

 

在DNS服務容器上抓到的包如下:

複製

1
2
16:57:07.628112 IP 172.20.92.100.36772 > nodexxxx.domain: 46851+ [1au] A? youku.com. (38)
16:57:07.628339 IP nodexxxx.47350 > 192.168.x.x.domain: 46851+ [1au] A? youku.com. (38)

 

並沒有多餘的DNS請求。

優化方式2:具體應用配置特定的 ndots

其實,往往我們還真不太好用這種絕對域名的方式,有誰請求youku.com的時候,還寫成 youku.com. 呢?

在 Kubernetes 中,默認設置了 ndots 值爲5,是因爲,Kubernetes 認爲,內部域名,最長爲5,要保證內部域名的請求,優先走集羣內部的DNS,而不是將內部域名的DNS解析請求,有打到外網的機會,Kubernetes 設置 ndots 爲5是一個比較合理的行爲。

如果你需要定製這個長度,最好是爲自己的業務,單獨配置 ndots 即可(Pod爲例,其實配置deployment最好)。

複製

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: dns-example
spec:
  containers:
    - name: test
      image: nginx
  dnsConfig:
    options:
      - name: ndots
        value: "1"

 

Kubernetes DNS 策略

在Kubernetes 中,有4種 DNS 策略,從 Kubernetes 源碼中看:

複製

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const (
	// DNSClusterFirstWithHostNet indicates that the pod should use cluster DNS
	// first, if it is available, then fall back on the default
	// (as determined by kubelet) DNS settings.
	DNSClusterFirstWithHostNet DNSPolicy = "ClusterFirstWithHostNet"

	// DNSClusterFirst indicates that the pod should use cluster DNS
	// first unless hostNetwork is true, if it is available, then
	// fall back on the default (as determined by kubelet) DNS settings.
	DNSClusterFirst DNSPolicy = "ClusterFirst"

	// DNSDefault indicates that the pod should use the default (as
	// determined by kubelet) DNS settings.
	DNSDefault DNSPolicy = "Default"

	// DNSNone indicates that the pod should use empty DNS settings. DNS
	// parameters such as nameservers and search paths should be defined via
	// DNSConfig.
	DNSNone DNSPolicy = "None"
)

 

這幾種DNS策略,需要在Pod,或者Deployment、RC等資源中,設置 dnsPolicy 即可,以 Pod 爲例:

複製

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
kind: Pod
metadata:
   labels:
    name: cadvisor-nodexxxx
    hostip: 192.168.x.x
  name: cadvisor-nodexxxx
  namespace: monitoring
spec:
  containers:
  - args:
    - --profiling
    - --housekeeping_interval=10s
    - --storage_duration=1m0s
    image: google/cadvisor:latest
    name: cadvisor-nodexxxx
    ports:
    - containerPort: 8080
      name: http
      protocol: TCP
    resources: {}
    securityContext:
      privileged: true
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
  dnsPolicy: ClusterFirst
  nodeName: nodexxxx

 

具體來說:

None

表示空的DNS設置
這種方式一般用於想要自定義 DNS 配置的場景,而且,往往需要和 dnsConfig 配合一起使用達到自定義 DNS 的目的。

Default

有人說 Default 的方式,是使用宿主機的方式,這種說法並不準確。
這種方式,其實是,讓 kubelet 來決定使用何種 DNS 策略。而 kubelet 默認的方式,就是使用宿主機的 /etc/resolv.conf(可能這就是有人說使用宿主機的DNS策略的方式吧),但是,kubelet 是可以靈活來配置使用什麼文件來進行DNS策略的,我們完全可以使用 kubelet 的參數:–resolv-conf=/etc/resolv.conf 來決定你的DNS解析文件地址。

ClusterFirst

這種方式,表示 POD 內的 DNS 使用集羣中配置的 DNS 服務,簡單來說,就是使用 Kubernetes 中 kubedns 或 coredns 服務進行域名解析。如果解析不成功,纔會使用宿主機的 DNS 配置進行解析。

ClusterFirstWithHostNet

在某些場景下,我們的 POD 是用 HOST 模式啓動的(HOST模式,是共享宿主機網絡的),一旦用 HOST 模式,表示這個 POD 中的所有容器,都要使用宿主機的 /etc/resolv.conf 配置進行DNS查詢,但如果你想使用了 HOST 模式,還繼續使用 Kubernetes 的DNS服務,那就將 dnsPolicy 設置爲 ClusterFirstWithHostNet。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章