先前我們說明了在K8S內部基本網路溝通的基本原則,本篇我們再針對應用服務的查詢與發現,這個部分將會依賴K8S內部的DNS元件來實現。
同時也將說明內部服務查詢的整個生命循環,我們可以透過了解DNS的工作原理,可以在應用服務的問題處理可以更有效率。
本篇將不會說明安裝的做法,只針對DNS服務查詢的流程進行討論。我們接著將說明以下內容:
一般來說,K8S內部使用Pod IP進行溝通會有以下問題:
動態IP指派
:Pod在建立、重啟、擴展都會被指派新的IP位址,以IP來追蹤的話會常常出現問題。外部存取限制
:Pod IP無法從外部存取,因為K8S是沒有路由能力的。所以外部要存取到Pod,需要透過Service
type。所謂的Service
type就是透過建立一個持久性的名稱來讓後端的Pod群組可以被外界存取到,並且提供以下優點:
Stable Communication
:當Pod要連線到其他的Pod時,直接參照Service name,並且service可以很有效的將流量路由到其他的PodLoad Balancing
: 透過負載平衡的特性,將流量分散到所有的Pod可以強化應用服務的效能High Availability
: Service設定可以確保流量的重導到特定的Pod,就算失敗也不會干擾應用服務的可用性。DNS Resolution
: Service可以向Kubernetes DNS進行註冊,讓Pod-to-Pod的溝通可以很容易建立,而且不需要知道個別的IP位址。用一句話來說明:將服務發現的工作簡單化
當我們建立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將建立一個以上的podkube-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
一個完整的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。
情況1:不同節點
流程:
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記錄
#--------------------------------------------------
# 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
#--------------------------------------------------
# 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
所有Pod一建立起來就會被指派IP。Pod之間就可以使用這些IP相互溝通。
問題是如果Pod有任何重建或是刪除的異動的話,原本的IP也會被異動。如果要避免這種情況就必須要改用Service
這種資源類型。
當建立service
之後,apiserver就會儲存相關的資料到controller-manager
並且會帶跟另外二個資源類型:Endpoints
與EndpointSlices
。
此時,CoreDNS就會利用這些資源去識別如何將service name轉換成service ip。同時,每個節點上的kube-proxy
就會去更新節點內的iptables rules
。這些規則就會讓需求可以正確定位到正確的Pod。
最後,當Pod產生查詢需求,就會執行一個DNS查詢給CoreDNS來取得service ip,取得service ip之後,就會套用節點上由kube-proxy
所建立的規則來將流量導至實際的Pod IP。
本篇說明到這邊,感謝大家觀看,下期再見~~~
※ References: