解密K8S DNS:揭開內部服務查詢的生命循環

2024/02/21閱讀時間約 18 分鐘

先前我們說明了在K8S內部基本網路溝通的基本原則,本篇我們再針對應用服務的查詢與發現,這個部分將會依賴K8S內部的DNS元件來實現。

raw-image

同時也將說明內部服務查詢的整個生命循環,我們可以透過了解DNS的工作原理,可以在應用服務的問題處理可以更有效率。

本篇將不會說明安裝的做法,只針對DNS服務查詢的流程進行討論。我們接著將說明以下內容:

  1. Pod在K8S內部的溝通
  2. Kubernetes DNS是什麼
  3. DNS record長什麼樣子
  4. 要如何利用DNS來找到服務
  5. 基本測試
  6. 結論

1. Pod在K8S內部的溝通

一般來說,K8S內部使用Pod IP進行溝通會有以下問題:

  1. 動態IP指派:Pod在建立、重啟、擴展都會被指派新的IP位址,以IP來追蹤的話會常常出現問題。
  2. 外部存取限制:Pod IP無法從外部存取,因為K8S是沒有路由能力的。所以外部要存取到Pod,需要透過Service type。

所謂的Service type就是透過建立一個持久性的名稱來讓後端的Pod群組可以被外界存取到,並且提供以下優點:

  • Stable Communication:當Pod要連線到其他的Pod時,直接參照Service name,並且service可以很有效的將流量路由到其他的Pod
  • Load Balancing : 透過負載平衡的特性,將流量分散到所有的Pod可以強化應用服務的效能
  • High Availability : Service設定可以確保流量的重導到特定的Pod,就算失敗也不會干擾應用服務的可用性。
  • DNS Resolution : Service可以向Kubernetes DNS進行註冊,讓Pod-to-Pod的溝通可以很容易建立,而且不需要知道個別的IP位址。

2. Kubernetes DNS是什麼

用一句話來說明:將服務發現的工作簡單化

當我們建立Service建立之後,K8S DNS就會自動產生一個對應的A record 將service dns name與IP位址配對。之後,Pod就可以透過DNS名稱來進行連線。而DNS負責動態更新所有的A record來反應Service IP的改變。

K8S核心元件預設將與內部的DNS進行溝通來確保每個Pod與Service建立、移動、移除時相關記錄的正確。

目前K8S預設的DNS已從kube-dns移動到CoreDNS。二者所實作的功能相同的部分如下:

  • kube-dns service將建立一個以上的pod
  • kube-dns service針對service與endpoint相關事件並且修改成合適的DNS記錄
  • kubelet會對kube-dns的service指派對應的cluster ip,並且在每個pod內部的/etc/resolv.conf加入nameserver的字串如下:
nameserver 10.10.23.1
search namespace.svc.cluster.local svc.cluster.local cluster.local
  • 應用程式可以將 test-service.namespace 等主機名稱解析為適當的叢集 IP 位址
raw-image

3. DNS record長什麼樣子

一個完整的k8s service的A record如下:

service.namespace.svc.cluster.local

對應於上述的記錄,對於實際上的real IP後呈現如下:

10-32-0-125.namespace.pod.cluster.local

同時,針對k8s service 特定port的SRV記錄將用以下格式建立:

_port-name._protocol.service.namespace.svc.cluster.local

最後,應用程式可透過簡單且一致的主機名稱來存取叢集上的其他服務或 Pod。

通常不會使用完整的hostname來取用其他的服務,因為會在resolv.conf會設定好domain suffixes,如果要與同一個namespace內的服務溝通的話,只要用以下方式即可:

other-service

如果要到其他的namespace的話,加上:

other-service.other-namespace

如果要直接對應到Pod的話,則是:

pod-ip.other-namespace.pod

一個標準的service的定義如下:

apiVersion: v1
kind: Service
metadata:
name: foo
namespace: bar
spec:
ports:
- port: 80
name: http

建立之後,A record, SRV record就會用以下方式產生:

foo.bar.svc.cluster.local                     30 A X.X.X.X
_http._tcp.nginx.default.svc.cluster.local 3600 SRV 0 100 80 X-X-X-X.foo.bar.svc.cluster.local.

※ Cluster domain: cluster.local

當記錄建立完成後,在cluster內就可以使用DNS name去解析Service IP。


4. 要如何利用DNS來找到服務

情況1:不同節點

raw-image

流程:

Step1. Pod發動一個DNS查詢,會先查自已內部的resolv.conf

Step2. 內部的resolv.conf會設定一個預設的dns,這個dns帶有快取的含義

Step3. 如果這個local dns沒有相關的記錄,就會透過resolv.conf的內容,指向CoreDNS

Step4. CoreDNS透過向Kubernetes API(service registry)進行查詢,這個registry就會包含service name與ip的對應

Step5. 當查詢到了之後就會將正確的值回傳給Pod

Step6. 如果在Service registry也沒有的記錄就會再往更上游的DNS進行查詢。

以下簡單說明:

※ Pod發起查詢

Pod發送一個API需求來查詢service時,先詢問自已/etc/resolv.conf的內容。這個檔案是由kubelet所自動為每個pod所產生的。預設內容如下:

# cat /etc/resolv.conf

search namespace.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.101.101.11
options ndots:5

※ ndots 選項決定何時直接進行絕對網域查詢,而不是先附加搜尋網域。

※ Local dns

DNS查詢在網路溝通的過程是很普遍發生的,因為頻率很高,所以必須要很快速地處理來避免造成效能問題並且不容易除錯。

如果在k8s cluster內要強化DNS查詢效率,可以在每個節點再加入一個nodelocaldns元件來做決DNS快取。

如果在快取層沒有找到記錄,就會將查詢向上到CoreDNS,這種方式可以大大減少不斷的往CoreDNS查詢的頻率。

※ K8S 記錄的TTL

預設CoreDNS的DNS記錄的TTL是30秒。並且可以在CoreDNS的設定檔內變更這個值。

TTL會決定在必須進行新查詢之前多久的回應將被視為有效。如果把這個TTL變的更短,反而有可能增加DNS server的負載。但是更長也可能導致DNS回應的過期或回應的值不正確的問題。

※ SRV

K8S也會使用SRV來解析服務Port號。這也允許客戶端可以透過查詢DNS來發現服務的Port號。例如:

apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: test
spec:
ports:
- port: 80
name: http

[note]
(1) nginx容器會以http的名稱,暴露80port給外界,而k8s將會自動產生一個SRV的記錄如下:
_http._tcp.nginx.tet.svc.cluster.local

(2)用指令直接查詢:
[root]# dig SRV _http._tcp.nginx.tet.svc.cluster.local +short
=> 會回傳對應的SRV記錄

5. 基本測試

#--------------------------------------------------
# S5-1. create dnsutils pod
#--------------------------------------------------
[root]# vim dnsutils.yaml
apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
- name: dnsutils
image: registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3
command:
- sleep
- "infinity"
imagePullPolicy: IfNotPresent
restartPolicy: Always

[root]# kubectl apply -f dnsutils.yaml
[root]# kubectl get pod
#--------------------------------------------------
# S5-2. confirm pod dns
#--------------------------------------------------
[root]# kubectl exec -i -t dnsutils -- nslookup kubernetes.default
Server: 10.96.0.10
Address: 10.96.0.10#53

Name: kubernetes.default.svc.cluster.local
Address: 10.96.0.1
#--------------------------------------------------
# S5-3. confirm dns resolv.conf
#--------------------------------------------------
[root]# kubectl exec -ti dnsutils -- cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local test.example.poc
nameserver 10.96.0.10
options ndots:5
#--------------------------------------------------
# S5-4. confirm coredns running
#--------------------------------------------------
[root]# kubectl get pods --namespace=kube-system -l k8s-app=kube-dns
NAME READY STATUS RESTARTS AGE
coredns-5d78c9869d-n5jwk 1/1 Running 2 90d
coredns-5d78c9869d-s2kf8 1/1 Running 2 90d
#--------------------------------------------------
# S5-5. confirm pod's log
#--------------------------------------------------
[root]# kubectl logs --namespace=kube-system -l k8s-app=kube-dns
raw-image
#--------------------------------------------------
# S5-6. confirm dns service is running
# "kube-dns"名稱是coredns & kubedns都用同一個名字
#--------------------------------------------------
[root]# kubectl get svc --namespace=kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 90d
metrics-server ClusterIP 10.103.38.187 <none> 443/TCP
#--------------------------------------------------
# S5-7. DNS endpoint是否有暴露出來
#--------------------------------------------------
[root]# kubectl get endpoints kube-dns --namespace=kube-system
NAME ENDPOINTS AGE
kube-dns 192.168.101.133:53,192.168.101.134:53,192.168.101.133:53 + 3 more..
#--------------------------------------------------
# S5-8. 確認DNS查詢是否有接收並處理
# 透過在coredns設定檔內加入log
#--------------------------------------------------
[root]# kubectl edit configmap coredns -n kube-system
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
log <<<
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}

[root]# kubectl logs -f coredns-5d78c9869d-n5jwk -n kube-system
raw-image

6.結論

所有Pod一建立起來就會被指派IP。Pod之間就可以使用這些IP相互溝通。

問題是如果Pod有任何重建或是刪除的異動的話,原本的IP也會被異動。如果要避免這種情況就必須要改用Service這種資源類型。

當建立service之後,apiserver就會儲存相關的資料到controller-manager並且會帶跟另外二個資源類型:EndpointsEndpointSlices

此時,CoreDNS就會利用這些資源去識別如何將service name轉換成service ip。同時,每個節點上的kube-proxy就會去更新節點內的iptables rules。這些規則就會讓需求可以正確定位到正確的Pod。

最後,當Pod產生查詢需求,就會執行一個DNS查詢給CoreDNS來取得service ip,取得service ip之後,就會套用節點上由kube-proxy所建立的規則來將流量導至實際的Pod IP。


本篇說明到這邊,感謝大家觀看,下期再見~~~


※ References:

8會員
39內容數
記錄IT社畜的自我學習筆記,如同專題名稱,主要是怕自已忘記自已做過什麼、學到什麼。索性就分享我自已在學習Kubernetes這條路上的各種測試、學習心得。
留言0
查看全部
發表第一個留言支持創作者!